/*
 *  reporting.c
 *
 *  Copyright 2013 - 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 <http://www.gnu.org/licenses/>.
 *
 *  See COPYING file for the complete license text.
 */

#include "libiec61850_platform_includes.h"
#include "mms_mapping.h"
#include "linked_list.h"
#include "array_list.h"
#include "stack_config.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"
#include "conversions.h"
#include "reporting.h"
#include <string.h>

#ifndef DEBUG_IED_SERVER
#define DEBUG_IED_SERVER 0
#endif

#if (CONFIG_IEC61850_REPORT_SERVICE == 1)

#ifndef CONFIG_IEC61850_BRCB_WITH_RESVTMS
#define CONFIG_IEC61850_BRCB_WITH_RESVTMS 0
#endif


#ifndef CONFIG_IEC61850_EDITION_1
#define CONFIG_IEC61850_EDITION_1 0
#endif

#if (CONFIG_IEC61850_EDITION_1 == 1)
#define CONFIG_REPORTING_SUPPORTS_OWNER 0
#endif

#ifndef CONFIG_REPORTING_SUPPORTS_OWNER
#define CONFIG_REPORTING_SUPPORTS_OWNER 1
#endif


static ReportBuffer*
ReportBuffer_create(void)
{
    ReportBuffer* self = (ReportBuffer*) GLOBAL_MALLOC(sizeof(ReportBuffer));
    self->lastEnqueuedReport = NULL;
    self->oldestReport = NULL;
    self->nextToTransmit = NULL;
    self->memoryBlockSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE;
    self->memoryBlock = (uint8_t*) GLOBAL_MALLOC(self->memoryBlockSize);
    self->reportsCount = 0;
    self->isOverflow = true;

    return self;
}

static void
ReportBuffer_destroy(ReportBuffer* self)
{
    GLOBAL_FREEMEM(self->memoryBlock);
    GLOBAL_FREEMEM(self);
}

ReportControl*
ReportControl_create(bool buffered, LogicalNode* parentLN)
{
    ReportControl* self = (ReportControl*) GLOBAL_MALLOC(sizeof(ReportControl));
    self->name = NULL;
    self->domain = NULL;
    self->parentLN = parentLN;
    self->rcbValues = NULL;
    self->confRev = NULL;
    self->enabled = false;
    self->reserved = false;
    self->buffered = buffered;
    self->isBuffering = false;
    self->isResync = false;
    self->gi = false;
    self->inclusionField = NULL;
    self->dataSet = NULL;
    self->isDynamicDataSet = false;
    self->clientConnection = NULL;
    self->intgPd = 0;
    self->sqNum = 0;
    self->nextIntgReportTime = 0;
    self->inclusionFlags = NULL;
    self->triggered = false;
    self->timeOfEntry = NULL;
    self->reservationTimeout = 0;
    self->triggerOps = 0;

#if (CONFIG_MMS_THREADLESS_STACK != 1)
    self->createNotificationsMutex = Semaphore_create(1);
#endif

    self->bufferedDataSetValues = NULL;
    self->valueReferences = NULL;
    self->lastEntryId = 0;

    if (buffered) {
        self->reportBuffer = ReportBuffer_create();
    }

    return self;
}

static void
ReportControl_lockNotify(ReportControl* self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_wait(self->createNotificationsMutex);
#endif
}

static void
ReportControl_unlockNotify(ReportControl* self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_post(self->createNotificationsMutex);
#endif
}


static void
purgeBuf(ReportControl* rc)
{
    if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: run purgeBuf\n");

    ReportBuffer* reportBuffer = rc->reportBuffer;

    reportBuffer->lastEnqueuedReport = NULL;
    reportBuffer->oldestReport = NULL;
    reportBuffer->nextToTransmit = NULL;
    reportBuffer->reportsCount = 0;
}


static void
deleteDataSetValuesShadowBuffer(ReportControl* self)
{
    if (self->bufferedDataSetValues != NULL) {
        assert(self->dataSet != NULL);

        int dataSetSize = DataSet_getSize(self->dataSet);

        int i;

        for (i = 0; i < dataSetSize; i++) {
            if (self->bufferedDataSetValues[i] != NULL)
                MmsValue_delete(self->bufferedDataSetValues[i]);
        }

        GLOBAL_FREEMEM(self->bufferedDataSetValues);

        if (self->valueReferences != NULL)
            GLOBAL_FREEMEM(self->valueReferences);

        self->bufferedDataSetValues = NULL;
    }
}

void
ReportControl_destroy(ReportControl* self)
{
    if (self->rcbValues != NULL )
        MmsValue_delete(self->rcbValues);

    if (self->inclusionFlags != NULL)
        GLOBAL_FREEMEM(self->inclusionFlags);

    if (self->inclusionField != NULL)
        MmsValue_delete(self->inclusionField);

    if (self->buffered == false)
        MmsValue_delete(self->timeOfEntry);

    deleteDataSetValuesShadowBuffer(self);

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

    if (self->buffered)
        ReportBuffer_destroy(self->reportBuffer);

#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_destroy(self->createNotificationsMutex);
#endif

    GLOBAL_FREEMEM(self->name);

    GLOBAL_FREEMEM(self);
}

MmsValue*
ReportControl_getRCBValue(ReportControl* rc, char* elementName)
{
    if (rc->buffered) {
        if (strcmp(elementName, "RptID") == 0)
            return MmsValue_getElement(rc->rcbValues, 0);
        else if (strcmp(elementName, "RptEna") == 0)
            return MmsValue_getElement(rc->rcbValues, 1);
        else if (strcmp(elementName, "DatSet") == 0)
            return MmsValue_getElement(rc->rcbValues, 2);
        else if (strcmp(elementName, "ConfRev") == 0)
            return MmsValue_getElement(rc->rcbValues, 3);
        else if (strcmp(elementName, "OptFlds") == 0)
            return MmsValue_getElement(rc->rcbValues, 4);
        else if (strcmp(elementName, "BufTm") == 0)
            return MmsValue_getElement(rc->rcbValues, 5);
        else if (strcmp(elementName, "SqNum") == 0)
            return MmsValue_getElement(rc->rcbValues, 6);
        else if (strcmp(elementName, "TrgOps") == 0)
            return MmsValue_getElement(rc->rcbValues, 7);
        else if (strcmp(elementName, "IntgPd") == 0)
            return MmsValue_getElement(rc->rcbValues, 8);
        else if (strcmp(elementName, "GI") == 0)
            return MmsValue_getElement(rc->rcbValues, 9);
        else if (strcmp(elementName, "PurgeBuf") == 0)
            return MmsValue_getElement(rc->rcbValues, 10);
        else if (strcmp(elementName, "EntryID") == 0)
            return MmsValue_getElement(rc->rcbValues, 11);
        else if (strcmp(elementName, "TimeofEntry") == 0)
            return MmsValue_getElement(rc->rcbValues, 12);
#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)
        else if (strcmp(elementName, "ResvTms") == 0)
            return MmsValue_getElement(rc->rcbValues, 13);
        else if (strcmp(elementName, "Owner") == 0)
            return MmsValue_getElement(rc->rcbValues, 14);
#else
        else if (strcmp(elementName, "Owner") == 0)
            return MmsValue_getElement(rc->rcbValues, 13);
#endif
    } else {
        if (strcmp(elementName, "RptID") == 0)
            return MmsValue_getElement(rc->rcbValues, 0);
        else if (strcmp(elementName, "RptEna") == 0)
            return MmsValue_getElement(rc->rcbValues, 1);
        else if (strcmp(elementName, "Resv") == 0)
            return MmsValue_getElement(rc->rcbValues, 2);
        else if (strcmp(elementName, "DatSet") == 0)
            return MmsValue_getElement(rc->rcbValues, 3);
        else if (strcmp(elementName, "ConfRev") == 0)
            return MmsValue_getElement(rc->rcbValues, 4);
        else if (strcmp(elementName, "OptFlds") == 0)
            return MmsValue_getElement(rc->rcbValues, 5);
        else if (strcmp(elementName, "BufTm") == 0)
            return MmsValue_getElement(rc->rcbValues, 6);
        else if (strcmp(elementName, "SqNum") == 0)
            return MmsValue_getElement(rc->rcbValues, 7);
        else if (strcmp(elementName, "TrgOps") == 0)
            return MmsValue_getElement(rc->rcbValues, 8);
        else if (strcmp(elementName, "IntgPd") == 0)
            return MmsValue_getElement(rc->rcbValues, 9);
        else if (strcmp(elementName, "GI") == 0)
            return MmsValue_getElement(rc->rcbValues, 10);
        else if (strcmp(elementName, "Owner") == 0)
            return MmsValue_getElement(rc->rcbValues, 11);
    }

    return NULL ;
}

static void
updateTimeOfEntry(ReportControl* self, uint64_t currentTime)
{
    MmsValue* timeOfEntry = self->timeOfEntry;
    MmsValue_setBinaryTime(timeOfEntry, currentTime);
}

static void
sendReport(ReportControl* self, bool isIntegrity, bool isGI)
{
    updateTimeOfEntry(self, Hal_getTimeInMs());

    LinkedList reportElements = LinkedList_create();

    LinkedList deletableElements = LinkedList_create();

    MmsValue* rptId = ReportControl_getRCBValue(self, "RptID");
    MmsValue* optFlds = ReportControl_getRCBValue(self, "OptFlds");
    MmsValue* datSet = ReportControl_getRCBValue(self, "DatSet");

    LinkedList_add(reportElements, rptId);
    LinkedList_add(reportElements, optFlds);

    /* delete option fields for unsupported options */
    MmsValue_setBitStringBit(optFlds, 7, false); /* entryID */
    MmsValue_setBitStringBit(optFlds, 9, false); /* segmentation */

    MmsValue* sqNum = ReportControl_getRCBValue(self, "SqNum");

    if (MmsValue_getBitStringBit(optFlds, 1)) /* sequence number */
        LinkedList_add(reportElements, sqNum);

    if (MmsValue_getBitStringBit(optFlds, 2)) /* report time stamp */
        LinkedList_add(reportElements, self->timeOfEntry);

    if (MmsValue_getBitStringBit(optFlds, 4)) /* data set reference */
        LinkedList_add(reportElements, datSet);

    if (MmsValue_getBitStringBit(optFlds, 6)) { /* bufOvfl */
        MmsValue* bufOvfl = MmsValue_newBoolean(false);

        LinkedList_add(reportElements, bufOvfl);
        LinkedList_add(deletableElements, bufOvfl);
    }

    if (MmsValue_getBitStringBit(optFlds, 8))
        LinkedList_add(reportElements, self->confRev);

    if (isGI || isIntegrity)
        MmsValue_setAllBitStringBits(self->inclusionField);
    else
        MmsValue_deleteAllBitStringBits(self->inclusionField);

    LinkedList_add(reportElements, self->inclusionField);

    /* add data references if selected */
    if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */
        DataSetEntry* dataSetEntry = self->dataSet->fcdas;

        LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent;

        IedModel* iedModel = (IedModel*) ld->parent;

        char* iedName = iedModel->name;

        int iedNameLength = strlen(iedName);

        int i = 0;

        for (i = 0; i < self->dataSet->elementCount; i++) {
            assert(dataSetEntry->value != NULL);

            bool addReferenceForEntry = false;

            if (isGI || isIntegrity)
               addReferenceForEntry = true;
            else
                if (self->inclusionFlags[i] != REPORT_CONTROL_NONE)
                    addReferenceForEntry = true;

            if (addReferenceForEntry) {

                char dataReference[130];
                int currentPos = 0;

                int j;

                for (j = 0; j < iedNameLength; j++) {
                    dataReference[currentPos++] = iedName[j];
                }

                int ldNameLength =  strlen(dataSetEntry->logicalDeviceName);
                for (j = 0; j <  ldNameLength; j++) {
                    dataReference[currentPos] = dataSetEntry->logicalDeviceName[j];
                    currentPos++;
                }

                dataReference[currentPos++] = '/';

                for (j = 0; j < (int) strlen(dataSetEntry->variableName); j++) {
                    dataReference[currentPos++] = dataSetEntry->variableName[j];
                }

                dataReference[currentPos] = 0;

                MmsValue* dataRef = MmsValue_newVisibleString(dataReference);

                LinkedList_add(reportElements, dataRef);
                LinkedList_add(deletableElements, dataRef);
            }

            dataSetEntry = dataSetEntry->sibling;

        }
    }

    /* add data set value elements */
    DataSetEntry* dataSetEntry = self->dataSet->fcdas;

    int i = 0;
    for (i = 0; i < self->dataSet->elementCount; i++) {
        assert(dataSetEntry->value != NULL);

        if (isGI || isIntegrity) {
            LinkedList_add(reportElements, dataSetEntry->value);
        }
        else {
            if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) {
                assert(self->bufferedDataSetValues[i] != NULL);

                LinkedList_add(reportElements, self->bufferedDataSetValues[i]);
                MmsValue_setBitStringBit(self->inclusionField, i, true);
            }
        }

        dataSetEntry = dataSetEntry->sibling;
    }

    /* add reason code to report if requested */
    if (MmsValue_getBitStringBit(optFlds, 3)) {
        for (i = 0; i < self->dataSet->elementCount; i++) {

            if (isGI || isIntegrity) {
                MmsValue* reason = MmsValue_newBitString(6);

                if (isGI)
                    MmsValue_setBitStringBit(reason, 5, true);

                if (isIntegrity)
                    MmsValue_setBitStringBit(reason, 4, true);

                LinkedList_add(reportElements, reason);
                LinkedList_add(deletableElements, reason);
            }
            else if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) {
                MmsValue* reason = MmsValue_newBitString(6);

                if (self->inclusionFlags[i] == REPORT_CONTROL_QUALITY_CHANGED)
                    MmsValue_setBitStringBit(reason, 2, true);
                else if (self->inclusionFlags[i] == REPORT_CONTROL_VALUE_CHANGED)
                    MmsValue_setBitStringBit(reason, 1, true);
                else if (self->inclusionFlags[i] == REPORT_CONTROL_VALUE_UPDATE)
                    MmsValue_setBitStringBit(reason, 3, true);

                LinkedList_add(reportElements, reason);
                LinkedList_add(deletableElements, reason);
            }
        }
    }

    /* clear inclusion flags */
    for (i = 0; i < self->dataSet->elementCount; i++)
        self->inclusionFlags[i] = REPORT_CONTROL_NONE;

    MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", reportElements, false);

    /* Increase sequence number */
    self->sqNum++;

    /* Unbuffered reporting --> sqNum is 8 bit only!!! */
    if (self->sqNum == 256)
        self->sqNum = 0;

    MmsValue_setUint16(sqNum, self->sqNum);

    LinkedList_destroyDeep(deletableElements, (LinkedListValueDeleteFunction) MmsValue_delete);
    LinkedList_destroyStatic(reportElements);
}

static void
createDataSetValuesShadowBuffer(ReportControl* rc)
{
    int dataSetSize = DataSet_getSize(rc->dataSet);

    MmsValue** dataSetValues = (MmsValue**) GLOBAL_CALLOC(dataSetSize, sizeof(MmsValue*));

    rc->bufferedDataSetValues = dataSetValues;

    rc->valueReferences = (MmsValue**) GLOBAL_MALLOC(dataSetSize * sizeof(MmsValue*));

    DataSetEntry* dataSetEntry = rc->dataSet->fcdas;

    int i;
    for (i = 0; i < dataSetSize; i++) {
        assert(dataSetEntry != NULL);

        rc->valueReferences[i] = dataSetEntry->value;

        dataSetEntry = dataSetEntry->sibling;
    }
}

static bool
updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, MmsServerConnection connection)
{
    bool success = false;

    MmsValue* dataSetValue;

    if (newDatSet != NULL) {
        if (strcmp(MmsValue_toString(newDatSet), "") == 0) {
            success = true;
            dataSetValue = NULL;

            if (rc->buffered) {
                rc->isBuffering = false;
                purgeBuf(rc);
            }
        }
        else
            dataSetValue = newDatSet;
    }
    else
        dataSetValue = ReportControl_getRCBValue(rc, "DatSet");

    if (rc->isDynamicDataSet) {
        if (rc->dataSet != NULL) {
            deleteDataSetValuesShadowBuffer(rc);
            MmsMapping_freeDynamicallyCreatedDataSet(rc->dataSet);
            rc->isDynamicDataSet = false;
            rc->dataSet = NULL;
        }
    }

    if (dataSetValue != NULL) {
        const char* dataSetName = MmsValue_toString(dataSetValue);

        DataSet* dataSet = IedModel_lookupDataSet(mapping->model, dataSetName);

#if (MMS_DYNAMIC_DATA_SETS == 1)
        if (dataSet == NULL) {
            dataSet = MmsMapping_getDomainSpecificDataSet(mapping, dataSetName);

            if (dataSet == NULL) {


                /* check if association specific data set is requested */


                if (dataSetName[0] == '@') {

                    if (rc->buffered == false) { /* for buffered report non-permanent datasets are not allowed */
                        if (connection != NULL) {
                            MmsNamedVariableList mmsVariableList
                                = MmsServerConnection_getNamedVariableList(connection, dataSetName + 1);

                            if (mmsVariableList != NULL)
                                dataSet = MmsMapping_createDataSetByNamedVariableList(mapping, mmsVariableList);
                        }
                    }

                }

                /* check for VMD specific data set */
                else if (dataSetName[0] == '/') {
                    MmsNamedVariableList mmsVariableList = MmsDevice_getNamedVariableListWithName(mapping->mmsDevice, dataSetName + 1);

                    if (mmsVariableList != NULL)
                        dataSet = MmsMapping_createDataSetByNamedVariableList(mapping, mmsVariableList);
                }
            }

            if (dataSet == NULL)
                goto exit_function;

            rc->isDynamicDataSet = true;

        }
        else
            rc->isDynamicDataSet = false;
#else

        if (dataSet == NULL)
            goto exit_function;

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

        deleteDataSetValuesShadowBuffer(rc);

        rc->dataSet = dataSet;

        createDataSetValuesShadowBuffer(rc);

        if (rc->inclusionField != NULL)
            MmsValue_delete(rc->inclusionField);

        rc->inclusionField = MmsValue_newBitString(dataSet->elementCount);

        if (rc->inclusionFlags != NULL)
            GLOBAL_FREEMEM(rc->inclusionFlags);

        rc->inclusionFlags = (ReportInclusionFlag*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(ReportInclusionFlag));

        success = true;

        rc->isBuffering = true;

        goto exit_function;
    }

exit_function:
    return success;
}

static char*
createDataSetReferenceForDefaultDataSet(ReportControlBlock* rcb, ReportControl* reportControl)
{
    char* dataSetReference;

    char* domainName = MmsDomain_getName(reportControl->domain);
    char* lnName = rcb->parent->name;

    dataSetReference = createString(5, domainName, "/", lnName, "$", rcb->dataSetName);

    return dataSetReference;
}


static MmsValue*
createOptFlds(ReportControlBlock* reportControlBlock)
{
    MmsValue* optFlds = MmsValue_newBitString(-10);
    uint8_t options = reportControlBlock->options;

    if (options & RPT_OPT_SEQ_NUM)
        MmsValue_setBitStringBit(optFlds, 1, true);
    if (options & RPT_OPT_TIME_STAMP)
        MmsValue_setBitStringBit(optFlds, 2, true);
    if (options & RPT_OPT_REASON_FOR_INCLUSION)
        MmsValue_setBitStringBit(optFlds, 3, true);
    if (options & RPT_OPT_DATA_SET)
        MmsValue_setBitStringBit(optFlds, 4, true);
    if (options & RPT_OPT_DATA_REFERENCE)
        MmsValue_setBitStringBit(optFlds, 5, true);
    if (options & RPT_OPT_BUFFER_OVERFLOW)
        MmsValue_setBitStringBit(optFlds, 6, true);
    if (options & RPT_OPT_ENTRY_ID)
        MmsValue_setBitStringBit(optFlds, 7, true);
    if (options & RPT_OPT_CONF_REV)
        MmsValue_setBitStringBit(optFlds, 8, true);

    return optFlds;
}

static MmsValue*
createTrgOps(ReportControlBlock* 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);
    if (triggerOps & TRG_OPT_GI)
        MmsValue_setBitStringBit(trgOps, 5, true);

    return trgOps;
}

static void
refreshTriggerOptions(ReportControl* rc)
{
    rc->triggerOps = 0;
    MmsValue* trgOps = ReportControl_getRCBValue(rc, "TrgOps");
    if (MmsValue_getBitStringBit(trgOps, 1))
        rc->triggerOps += TRG_OPT_DATA_CHANGED;

    if (MmsValue_getBitStringBit(trgOps, 2))
        rc->triggerOps += TRG_OPT_QUALITY_CHANGED;

    if (MmsValue_getBitStringBit(trgOps, 3))
        rc->triggerOps += TRG_OPT_DATA_UPDATE;

    if (MmsValue_getBitStringBit(trgOps, 4))
        rc->triggerOps += TRG_OPT_INTEGRITY;

    if (MmsValue_getBitStringBit(trgOps, 5))
        rc->triggerOps += TRG_OPT_GI;
}

static void
refreshIntegrityPeriod(ReportControl* rc)
{
    MmsValue* intgPd = ReportControl_getRCBValue(rc, "IntgPd");
    rc->intgPd = MmsValue_toUint32(intgPd);

    if (rc->buffered == false)
        rc->nextIntgReportTime = 0;
}

static void
refreshBufferTime(ReportControl* rc)
{
    MmsValue* bufTm = ReportControl_getRCBValue(rc, "BufTm");
    rc->bufTm = MmsValue_toUint32(bufTm);
}

static void
composeDefaultRptIdString(char* rptIdString, ReportControl* reportControl)
{
    int bufPos = 0;
    while (reportControl->domain->domainName[bufPos] != 0) {
        rptIdString[bufPos] = reportControl->domain->domainName[bufPos];
        bufPos++;
    }
    rptIdString[bufPos++] = '/';
    int i = 0;
    while (reportControl->name[i] != 0) {
        rptIdString[bufPos] = reportControl->name[i];
        bufPos++;
        i++;
    }
    rptIdString[bufPos] = 0;
}

static MmsValue*
createDefaultRptId(ReportControl* reportControl)
{
    char rptIdString[130]; /* maximum length 129 chars */

    composeDefaultRptIdString(rptIdString, reportControl);

    return MmsValue_newVisibleString(rptIdString);
}

static void
updateWithDefaultRptId(ReportControl* reportControl, MmsValue* rptId)
{
    char rptIdString[130]; /* maximum length 129 chars */

    composeDefaultRptIdString(rptIdString, reportControl);

    MmsValue_setVisibleString(rptId, rptIdString);
}

static MmsVariableSpecification*
createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock,
        ReportControl* reportControl)
{
    MmsVariableSpecification* rcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    rcb->name = copyString(reportControlBlock->name);
    rcb->type = MMS_STRUCTURE;

    MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
    mmsValue->deleteValue = false;
    mmsValue->type = MMS_STRUCTURE;

#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1)
    int structSize = 12;
#else
    int structSize = 11;
#endif

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

    MmsVariableSpecification* namedVariable = 
			(MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("RptID");
    namedVariable->typeSpec.visibleString = -129;
    namedVariable->type = MMS_VISIBLE_STRING;
    rcb->typeSpec.structure.elements[0] = namedVariable;
    if ((reportControlBlock->rptId != NULL) && (strlen(reportControlBlock->rptId) > 0))
        mmsValue->value.structure.components[0] = MmsValue_newVisibleString(
                reportControlBlock->rptId);
    else
        mmsValue->value.structure.components[0] = createDefaultRptId(reportControl);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("RptEna");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[1] = namedVariable;
    mmsValue->value.structure.components[1] = MmsValue_newBoolean(false);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("Resv");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[2] = namedVariable;
    mmsValue->value.structure.components[2] = MmsValue_newBoolean(false);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("DatSet");
    namedVariable->typeSpec.visibleString = -129;
    namedVariable->type = MMS_VISIBLE_STRING;
    rcb->typeSpec.structure.elements[3] = namedVariable;

    if (reportControlBlock->dataSetName != NULL) {
    	char* dataSetReference = createDataSetReferenceForDefaultDataSet(reportControlBlock,
    	            reportControl);
    	mmsValue->value.structure.components[3] = MmsValue_newVisibleString(dataSetReference);
    	GLOBAL_FREEMEM(dataSetReference);
    }
    else
    	mmsValue->value.structure.components[3] = MmsValue_newVisibleString("");

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("ConfRev");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 32;
    rcb->typeSpec.structure.elements[4] = namedVariable;
    mmsValue->value.structure.components[4] =
            MmsValue_newUnsignedFromUint32(reportControlBlock->confRef);

    reportControl->confRev = mmsValue->value.structure.components[4];

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("OptFlds");
    namedVariable->type = MMS_BIT_STRING;
    namedVariable->typeSpec.bitString = -10;
    rcb->typeSpec.structure.elements[5] = namedVariable;
    mmsValue->value.structure.components[5] = createOptFlds(reportControlBlock);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("BufTm");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 32;
    rcb->typeSpec.structure.elements[6] = namedVariable;
    mmsValue->value.structure.components[6] =
            MmsValue_newUnsignedFromUint32(reportControlBlock->bufferTime);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("SqNum");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 8;
    rcb->typeSpec.structure.elements[7] = namedVariable;
    mmsValue->value.structure.components[7] = MmsValue_newUnsigned(8);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("TrgOps");
    namedVariable->type = MMS_BIT_STRING;
    namedVariable->typeSpec.bitString = -6;
    rcb->typeSpec.structure.elements[8] = namedVariable;
    mmsValue->value.structure.components[8] = createTrgOps(reportControlBlock);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("IntgPd");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 32;
    rcb->typeSpec.structure.elements[9] = namedVariable;
    mmsValue->value.structure.components[9] =
            MmsValue_newUnsignedFromUint32(reportControlBlock->intPeriod);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("GI");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[10] = namedVariable;
    mmsValue->value.structure.components[10] = MmsValue_newBoolean(false);

#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1)
    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("Owner");
    namedVariable->type = MMS_OCTET_STRING;
    namedVariable->typeSpec.octetString = -64;
    rcb->typeSpec.structure.elements[11] = namedVariable;
    mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 128);
#endif /* (CONFIG_REPORTING_SUPPORTS_OWNER == 1) */

    reportControl->rcbValues = mmsValue;

    reportControl->timeOfEntry = MmsValue_newBinaryTime(false);

    refreshBufferTime(reportControl);
    refreshIntegrityPeriod(reportControl);
    refreshTriggerOptions(reportControl);

    return rcb;
}

static MmsVariableSpecification*
createBufferedReportControlBlock(ReportControlBlock* reportControlBlock,
        ReportControl* reportControl, MmsMapping* mmsMapping)
{
    MmsVariableSpecification* rcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    rcb->name = copyString(reportControlBlock->name);
    rcb->type = MMS_STRUCTURE;

    int brcbElementCount = 13;

#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)
    brcbElementCount++;
#endif

#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1)
    brcbElementCount++;
#endif

    MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
    mmsValue->deleteValue = false;
    mmsValue->type = MMS_STRUCTURE;
    mmsValue->value.structure.size = brcbElementCount;
    mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(brcbElementCount, sizeof(MmsValue*));

    rcb->typeSpec.structure.elementCount = brcbElementCount;
    rcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(brcbElementCount,
            sizeof(MmsVariableSpecification*));

    MmsVariableSpecification* namedVariable = 
			(MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("RptID");
    namedVariable->typeSpec.visibleString = -129;
    namedVariable->type = MMS_VISIBLE_STRING;
    rcb->typeSpec.structure.elements[0] = namedVariable;

    if ((reportControlBlock->rptId != NULL) && (strlen(reportControlBlock->rptId) > 0))
        mmsValue->value.structure.components[0] = MmsValue_newVisibleString(
                reportControlBlock->rptId);
    else
        mmsValue->value.structure.components[0] = createDefaultRptId(reportControl);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("RptEna");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[1] = namedVariable;
    mmsValue->value.structure.components[1] = MmsValue_newBoolean(false);

    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 (reportControlBlock->dataSetName != NULL) {
    	char* dataSetReference = createDataSetReferenceForDefaultDataSet(reportControlBlock,
    	            reportControl);

    	mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference);
    	GLOBAL_FREEMEM(dataSetReference);
    }
    else
    	mmsValue->value.structure.components[2] = MmsValue_newVisibleString("");

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("ConfRev");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 32;
    rcb->typeSpec.structure.elements[3] = namedVariable;
    mmsValue->value.structure.components[3] =
            MmsValue_newUnsignedFromUint32(reportControlBlock->confRef);

    reportControl->confRev = mmsValue->value.structure.components[3];

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("OptFlds");
    namedVariable->type = MMS_BIT_STRING;
    namedVariable->typeSpec.bitString = -10;
    rcb->typeSpec.structure.elements[4] = namedVariable;
    mmsValue->value.structure.components[4] = createOptFlds(reportControlBlock);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("BufTm");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 32;
    rcb->typeSpec.structure.elements[5] = namedVariable;
    mmsValue->value.structure.components[5] =
            MmsValue_newUnsignedFromUint32(reportControlBlock->bufferTime);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("SqNum");
    namedVariable->type = MMS_UNSIGNED;
    namedVariable->typeSpec.unsignedInteger = 16;
    rcb->typeSpec.structure.elements[6] = namedVariable;
    mmsValue->value.structure.components[6] = MmsValue_newUnsigned(16);

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

    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(reportControlBlock->intPeriod);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("GI");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[9] = namedVariable;
    mmsValue->value.structure.components[9] = MmsValue_newBoolean(false);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("PurgeBuf");
    namedVariable->type = MMS_BOOLEAN;
    rcb->typeSpec.structure.elements[10] = namedVariable;
    mmsValue->value.structure.components[10] = MmsValue_newBoolean(false);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("EntryID");
    namedVariable->type = MMS_OCTET_STRING;
    namedVariable->typeSpec.octetString = 8;
    rcb->typeSpec.structure.elements[11] = namedVariable;
    mmsValue->value.structure.components[11] = MmsValue_newOctetString(8, 8);

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("TimeofEntry");
    namedVariable->type = MMS_BINARY_TIME;
    namedVariable->typeSpec.binaryTime = 6;
    rcb->typeSpec.structure.elements[12] = namedVariable;
    mmsValue->value.structure.components[12] = MmsValue_newBinaryTime(false);

    reportControl->timeOfEntry = mmsValue->value.structure.components[12];

#if ((CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) || (CONFIG_REPORTING_SUPPORTS_OWNER == 1))
    int currentIndex = 13;
#endif

#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)
    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("ResvTms");
    namedVariable->type = MMS_INTEGER;
    namedVariable->typeSpec.integer = 16;
    rcb->typeSpec.structure.elements[currentIndex] = namedVariable;
    mmsValue->value.structure.components[currentIndex] = MmsValue_newInteger(16);
    currentIndex++;
#endif /* (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) */

#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1)
    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("Owner");
    namedVariable->type = MMS_OCTET_STRING;
    namedVariable->typeSpec.octetString = -64;
    rcb->typeSpec.structure.elements[currentIndex] = namedVariable;
    mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 128); /* size 4 is enough to store client IPv4 address */
#endif /* (CONFIG_REPORTING_SUPPORTS_OWNER == 1) */

    reportControl->rcbValues = mmsValue;

    /* get initial values from BRCB */
    refreshBufferTime(reportControl);
    refreshIntegrityPeriod(reportControl);
    refreshTriggerOptions(reportControl);

    return rcb;
} /* createBufferedReportControlBlock() */

static ReportControlBlock*
getRCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode,
        int index, bool buffered)
{
    int rcbCount = 0;

    ReportControlBlock* nextRcb = self->model->rcbs;

    while (nextRcb != NULL ) {
        if (nextRcb->parent == logicalNode) {

            if (nextRcb->buffered == buffered) {
                if (rcbCount == index)
                    return nextRcb;

                rcbCount++;
            }

        }

        nextRcb = nextRcb->sibling;
    }

    return NULL ;
}

MmsVariableSpecification*
Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain,
        LogicalNode* logicalNode, int reportsCount)
{
    MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1,
            sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("BR");
    namedVariable->type = MMS_STRUCTURE;

    namedVariable->typeSpec.structure.elementCount = reportsCount;
    namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(reportsCount,
            sizeof(MmsVariableSpecification*));

    int currentReport = 0;

    while (currentReport < reportsCount) {
        ReportControl* rc = ReportControl_create(true, logicalNode);

        rc->domain = domain;

        ReportControlBlock* reportControlBlock = getRCBForLogicalNodeWithIndex(
                self, logicalNode, currentReport, true);

        rc->name = createString(3, logicalNode->name, "$BR$",
                reportControlBlock->name);

        namedVariable->typeSpec.structure.elements[currentReport] =
                createBufferedReportControlBlock(reportControlBlock, rc, self);

        LinkedList_add(self->reportControls, rc);

        currentReport++;
    }

    return namedVariable;
}

MmsVariableSpecification*
Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain,
        LogicalNode* logicalNode, int reportsCount)
{
    MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1,
            sizeof(MmsVariableSpecification));
    namedVariable->name = copyString("RP");
    namedVariable->type = MMS_STRUCTURE;

    namedVariable->typeSpec.structure.elementCount = reportsCount;
    namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(reportsCount,
            sizeof(MmsVariableSpecification*));

    int currentReport = 0;

    while (currentReport < reportsCount) {
        ReportControl* rc = ReportControl_create(false, logicalNode);

        rc->domain = domain;

        ReportControlBlock* reportControlBlock = getRCBForLogicalNodeWithIndex(
                self, logicalNode, currentReport, false);

        rc->name = createString(3, logicalNode->name, "$RP$",
                reportControlBlock->name);

        namedVariable->typeSpec.structure.elements[currentReport] =
                createUnbufferedReportControlBlock(reportControlBlock, rc);

        LinkedList_add(self->reportControls, rc);

        currentReport++;
    }

    return namedVariable;
}

static void
updateOwner(ReportControl* rc, MmsServerConnection connection)
{
    rc->clientConnection = connection;

#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1)
    MmsValue* owner = ReportControl_getRCBValue(rc, "Owner");

    if (owner != NULL) {

        if (connection != NULL) {
            char* clientAddressString = MmsServerConnection_getClientAddress(connection);

            if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: set owner to %s\n", clientAddressString);

            if (strchr(clientAddressString, '.') != NULL) {
                if (DEBUG_IED_SERVER)
                    printf("IED_SERVER: reporting.c:   client address is IPv4 address\n");

                uint8_t ipV4Addr[4];

                int addrElementCount = 0;

                char* separator = clientAddressString;

                while (separator != NULL && addrElementCount < 4) {
                    int intVal = atoi(separator);

                    ipV4Addr[addrElementCount] = intVal;

                    separator = strchr(separator, '.');

                    if (separator != NULL)
                        separator++; // skip '.' character

                    addrElementCount ++;
                }

                if (addrElementCount == 4)
                    MmsValue_setOctetString(owner, ipV4Addr, 4);
                else
                    MmsValue_setOctetString(owner, ipV4Addr, 0);

            }
            else {
                uint8_t ipV6Addr[16];
                MmsValue_setOctetString(owner, ipV6Addr, 0);
                if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c:   client address is IPv6 address or unknown\n");
            }
        }
        else {
            uint8_t emptyAddr[1];
            MmsValue_setOctetString(owner, emptyAddr, 0);
        }
    }

#endif /* CONFIG_REPORTING_SUPPORTS_OWNER == 1*/
}


static bool
checkForZeroEntryID(MmsValue* value)
{
    uint8_t* buffer = MmsValue_getOctetStringBuffer(value);

    int i = 0;

    while (i < 8) {
        if (buffer[i] != 0)
            return false;

        i++;
    }

    return true;
}

static bool
checkReportBufferForEntryID(ReportControl* rc, MmsValue* value)
{
    bool retVal = false;

    ReportBufferEntry* entry = rc->reportBuffer->oldestReport;

    while (entry != NULL) {
        if (memcmp(entry->entryId, value->value.octetString.buf, 8) == 0) {
            ReportBufferEntry* nextEntryForResync = entry->next;

            rc->reportBuffer->nextToTransmit = nextEntryForResync;
            rc->isResync = true;

            retVal = true;
            break;
        }

        entry = entry->next;
    }

    return retVal;
}

static void
increaseConfRev(ReportControl* self)
{
    uint32_t confRev = MmsValue_toUint32(self->confRev);

    confRev++;

    if (confRev == 0)
        confRev = 1;

    MmsValue_setUint32(self->confRev, confRev);
}

MmsDataAccessError
Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value,
        MmsServerConnection connection)
{
    MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS;

    ReportControl_lockNotify(rc);

    if (strcmp(elementName, "RptEna") == 0) {

        if (value->value.boolean == true) {

            if (rc->enabled == true) {
                retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
                goto exit_function;
            }

            if (DEBUG_IED_SERVER)
                printf("IED_SERVER: Activate report for client %s\n",
                        MmsServerConnection_getClientAddress(connection));

            if (updateReportDataset(self, rc, NULL, connection)) {

                updateOwner(rc, connection);

                MmsValue* rptEna = ReportControl_getRCBValue(rc, "RptEna");

                MmsValue_update(rptEna, value);

                if (rc->buffered) {

                    if (rc->isResync == false) {
                        rc->reportBuffer->nextToTransmit = rc->reportBuffer->oldestReport;
                        rc->reportBuffer->isOverflow = true;
                    }

                    rc->isResync = false;
                }

                rc->enabled = true;
                rc->gi = false;

                refreshBufferTime(rc);
                refreshTriggerOptions(rc);
                refreshIntegrityPeriod(rc);

                rc->sqNum = 0;

                MmsValue* sqNum = ReportControl_getRCBValue(rc, "SqNum");

                MmsValue_setUint32(sqNum, 0U);

                retVal = DATA_ACCESS_ERROR_SUCCESS;
                goto exit_function;
            }
            else {
                retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
                goto exit_function;
            }
        }
        else {
            if (rc->enabled == false)
                goto exit_function;

            if (((rc->enabled) || (rc->reserved)) && (rc->clientConnection != connection)) {
                retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
                goto exit_function;
            }

            if (DEBUG_IED_SERVER)
                printf("IED_SERVER: Deactivate report for client %s\n",
                        MmsServerConnection_getClientAddress(connection));

            if (rc->buffered) {
                rc->reportBuffer->isOverflow = true;
                rc->isResync = false;
            }
            else {
                GLOBAL_FREEMEM(rc->inclusionFlags);
                rc->inclusionFlags = NULL;

                MmsValue* resv = ReportControl_getRCBValue(rc, "Resv");
                MmsValue_setBoolean(resv, false);

                rc->triggered = false;

                rc->reserved = false;
            }

            updateOwner(rc, NULL);

            rc->enabled = false;
        }

    }

    if (strcmp(elementName, "GI") == 0) {
        if ((rc->enabled) && (rc->clientConnection == connection)) {

            if (MmsValue_getBoolean(value)) {
                if (rc->triggerOps & TRG_OPT_GI)
                    rc->gi = true;
            }

            retVal = DATA_ACCESS_ERROR_SUCCESS;
            goto exit_function;
        }
        else {
            retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
            goto exit_function;
        }
    }

    if (rc->enabled == false) {

        if ((rc->reserved) && (rc->clientConnection != connection)) {
            retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
            goto exit_function;
        }

        if (strcmp(elementName, "Resv") == 0) {
            rc->reserved = value->value.boolean;

            if (rc->reserved == true)
                rc->clientConnection = connection;
        }
        else if (strcmp(elementName, "PurgeBuf") == 0) {
            if (MmsValue_getType(value) == MMS_BOOLEAN) {

                if (MmsValue_getBoolean(value) == true) {
                    purgeBuf(rc);
                    retVal = DATA_ACCESS_ERROR_SUCCESS;
                    goto exit_function;
                }
            }

        }
        else if (strcmp(elementName, "DatSet") == 0) {
            MmsValue* datSet = ReportControl_getRCBValue(rc, "DatSet");

            if (!MmsValue_equals(datSet, value)) {

                if (updateReportDataset(self, rc, value, connection)) {

                    if (rc->buffered)
                        purgeBuf(rc);

                    MmsValue_update(datSet, value);

                    increaseConfRev(rc);
                }
                else {
                    retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
                    goto exit_function;
                }
            }

            retVal = DATA_ACCESS_ERROR_SUCCESS;
            goto exit_function;
        }
        else if (strcmp(elementName, "IntgPd") == 0) {
            MmsValue* intgPd = ReportControl_getRCBValue(rc, elementName);

            if (!MmsValue_equals(intgPd, value)) {
                MmsValue_update(intgPd, value);

                refreshIntegrityPeriod(rc);

                if (rc->buffered) {
                    rc->nextIntgReportTime = 0;
                    purgeBuf(rc);
                }
            }

            goto exit_function;
        }
        else if (strcmp(elementName, "TrgOps") == 0) {
            MmsValue* trgOps = ReportControl_getRCBValue(rc, elementName);

            if (!MmsValue_equals(trgOps, value)) {
                MmsValue_update(trgOps, value);

                if (rc->buffered)
                    purgeBuf(rc);

                refreshTriggerOptions(rc);
            }

            goto exit_function;
        }
        else if (strcmp(elementName, "EntryID") == 0) {

            if (MmsValue_getOctetStringSize(value) != 8) {
                retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
                goto exit_function;
            }

            if (checkForZeroEntryID(value) == false) {

                if (!checkReportBufferForEntryID(rc, value)) {
                    rc->reportBuffer->isOverflow = true;
                    retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
                    goto exit_function;
                }

                rc->reportBuffer->isOverflow = false;
            }
            else {
                rc->reportBuffer->nextToTransmit = rc->reportBuffer->oldestReport;
                rc->reportBuffer->isOverflow = true;
                rc->isResync = false;
            }

            MmsValue* entryID = ReportControl_getRCBValue(rc, elementName);
            MmsValue_update(entryID, value);

            goto exit_function;
        }

        else if (strcmp(elementName, "BufTm") == 0) {
            MmsValue* bufTm = ReportControl_getRCBValue(rc, elementName);

            if (!MmsValue_equals(bufTm, value)) {
                MmsValue_update(bufTm, value);

                if (rc->buffered)
                    purgeBuf(rc);

                refreshBufferTime(rc);
            }

            goto exit_function;
        }
        else if (strcmp(elementName, "RptID") == 0) {
            MmsValue* rptId = ReportControl_getRCBValue(rc, elementName);

            if (rc->buffered)
                purgeBuf(rc);

            if (strlen(MmsValue_toString(value)) == 0)
                updateWithDefaultRptId(rc, rptId);
            else
                MmsValue_update(rptId, value);

            goto exit_function;
        }
        else if (strcmp(elementName, "ConfRev") == 0) {
            retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto exit_function;
        }
        else if (strcmp(elementName, "SqNum") == 0) {
            retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto exit_function;
        }
        else if (strcmp(elementName, "Owner") == 0) {
            retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto exit_function;
        }
        else if (strcmp(elementName, "TimeofEntry") == 0) {
            retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto exit_function;
        }

        MmsValue* rcbValue = ReportControl_getRCBValue(rc, elementName);

        if (rcbValue != NULL)
            MmsValue_update(rcbValue, value);
        else {
            retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
            goto exit_function;
        }

    }
    else
        retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

exit_function:
    ReportControl_unlockNotify(rc);

    return retVal;
}

void
Reporting_deactivateReportsForConnection(MmsMapping* self, MmsServerConnection connection)
{
    LinkedList reportControl = self->reportControls;

    while ((reportControl = LinkedList_getNext(reportControl)) != NULL) {
        ReportControl* rc = (ReportControl*) reportControl->data;

        if (rc->clientConnection == connection) {

            rc->enabled = false;
            rc->clientConnection = NULL;

            MmsValue* rptEna = ReportControl_getRCBValue(rc, "RptEna");
            MmsValue_setBoolean(rptEna, false);

            if (rc->buffered == false) {

                if (rc->inclusionField != NULL) {
                    MmsValue_delete(rc->inclusionField);
                    rc->inclusionField = NULL;
                }

                MmsValue* resv = ReportControl_getRCBValue(rc, "Resv");
                MmsValue_setBoolean(resv, false);

                rc->reserved = false;
            }

            updateOwner(rc, NULL);
        }
    }
}

#if (DEBUG_IED_SERVER == 1)
static void
printEnqueuedReports(ReportControl* reportControl)
{
    ReportBuffer* rb = reportControl->reportBuffer;

#if 0
    printf("IED_SERVER: --- Enqueued reports ---\n");

    if (rb->oldestReport == NULL) {
        printf("IED_SERVER:   -- no reports --\n");
    }
    else {
        ReportBufferEntry* entry = rb->oldestReport;

        while (entry != NULL) {
            printf("IED_SERVER: ");

            int i = 0;
            printf("  ");
            for (i = 0; i < 8; i++) {
                printf("%02x ", entry->entryId[i]);
            }
            printf(" at [%p] next [%p] (len=%i pos=%i)", entry, entry->next,
                    entry->entryLength, (int) ((uint8_t*) entry - rb->memoryBlock));

            if (entry == rb->lastEnqueuedReport)
                printf("   <-- lastEnqueued");

            if (entry == rb->nextToTransmit)
                printf("   <-- nexttoTransmit");

            printf("\n");

            entry = entry->next;
        }
    }
#endif
    printf("IED_SERVER:   BRCB %s reports: %i\n", reportControl->name, rb->reportsCount);
    printf("IED_SERVER: -------------------------\n");
}

static void
printReportId(ReportBufferEntry* report)
{
    int i = 0;
    for (i = 0; i < 8; i++) {
        printf("%02x ", report->entryId[i]);
    }
}
#endif


static void
removeAllGIReportsFromReportBuffer(ReportBuffer* reportBuffer)
{
    printf("removeAllGIReportsFromReportBuffer\n");

    ReportBufferEntry* currentReport = reportBuffer->oldestReport;
    ReportBufferEntry* lastReport = NULL;

    while (currentReport != NULL) {
        if (currentReport->flags & 2) {

            if (currentReport == reportBuffer->oldestReport) {
                reportBuffer->oldestReport = currentReport->next;
            }
            else {
                lastReport->next = currentReport->next;

            }

#if (DEBUG_IED_SERVER == 1)
                printf("IED_SERVER:   REMOVE old GI report with ID ");
                printReportId(currentReport);
                printf("\n");
#endif

            if (reportBuffer->nextToTransmit == currentReport)
                reportBuffer->nextToTransmit = currentReport->next;

            currentReport = currentReport->next;
        }
        else {
            lastReport = currentReport;
            currentReport = currentReport->next;
        }
    }
}

static void
enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_t timeOfEntry)
{
   // 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);

    updateTimeOfEntry(reportControl, Hal_getTimeInMs());

    ReportBuffer* buffer = reportControl->reportBuffer;

    /* calculate size of complete buffer entry */
    int bufferEntrySize = sizeof(ReportBufferEntry);

    int inclusionFieldSize = MmsValue_getBitStringByteSize(reportControl->inclusionField);

    MmsValue inclusionFieldStatic;

    inclusionFieldStatic.type = MMS_BIT_STRING;
    inclusionFieldStatic.value.bitString.size = MmsValue_getBitStringSize(reportControl->inclusionField);

    MmsValue* inclusionField = &inclusionFieldStatic;

    if (isIntegrity || isGI) {

        DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas;

        int i;

        for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) {
            assert(dataSetEntry != NULL);

            bufferEntrySize += 1; /* reason-for-inclusion */

            bufferEntrySize += MmsValue_getSizeInMemory(dataSetEntry->value);

            dataSetEntry = dataSetEntry->sibling;
        }
    }
    else { /* other trigger reason */
        bufferEntrySize += inclusionFieldSize;

        int i;

        for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) {

            if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) {
                bufferEntrySize += 1; /* reason-for-inclusion */

                assert(reportControl->bufferedDataSetValues[i] != NULL);

                bufferEntrySize += MmsValue_getSizeInMemory(reportControl->bufferedDataSetValues[i]);
            }
        }
    }

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: enqueueReport: bufferEntrySize: %i\n", bufferEntrySize);

    if (bufferEntrySize > buffer->memoryBlockSize) {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: enqueueReport: report buffer too small for report entry! Skip event!\n");

        goto exit_function;
    }

    if (isGI) removeAllGIReportsFromReportBuffer(buffer);

    uint8_t* entryBufPos = NULL;
    uint8_t* entryStartPos;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: number of buffered reports:%i\n", buffer->reportsCount);

    if (buffer->lastEnqueuedReport == NULL) { /* buffer is empty - we start at the beginning of the memory block */
        entryBufPos = buffer->memoryBlock;
        buffer->oldestReport = (ReportBufferEntry*) entryBufPos;
        buffer->nextToTransmit = (ReportBufferEntry*) entryBufPos;
    }
    else {

        assert(buffer->lastEnqueuedReport != NULL);
        assert(buffer->oldestReport != NULL);

        if (DEBUG_IED_SERVER) {
            printf("IED_SERVER:   buffer->lastEnqueuedReport: %p\n", buffer->lastEnqueuedReport);
            printf("IED_SERVER:   buffer->oldestReport: %p\n", buffer->oldestReport);
            printf("IED_SERVER:   buffer->nextToTransmit: %p\n", buffer->nextToTransmit);
        }

        if (DEBUG_IED_SERVER) printf ("IED_SERVER: Last buffer offset: %i\n", (int) ((uint8_t*) buffer->lastEnqueuedReport - buffer->memoryBlock));

        if (buffer->lastEnqueuedReport == buffer->oldestReport) { /* --> buffer->reportsCount == 1 */
            assert(buffer->reportsCount == 1);

            entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);

            if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
                entryBufPos = buffer->memoryBlock;

#if (DEBUG_IED_SERVER == 1)
                printf("IED_SERVER: REMOVE report with ID ");
                printReportId(buffer->oldestReport);
                printf("\n");
#endif

                buffer->reportsCount = 0;
                buffer->oldestReport = (ReportBufferEntry*) entryBufPos;
                buffer->oldestReport->next = NULL;
                buffer->nextToTransmit = NULL;
            }
            else {
                if (buffer->nextToTransmit == buffer->oldestReport)
                    buffer->nextToTransmit = buffer->lastEnqueuedReport;

                buffer->oldestReport = buffer->lastEnqueuedReport;
                buffer->oldestReport->next = (ReportBufferEntry*) entryBufPos;
            }

        }
        else if (buffer->lastEnqueuedReport > buffer->oldestReport) {
            entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);

            if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
                entryBufPos = buffer->memoryBlock;

                /* remove old reports until enough space for new entry is available */
                while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) {
                    assert(buffer->oldestReport != NULL);

                    if (buffer->nextToTransmit == buffer->oldestReport) {
                        buffer->nextToTransmit = buffer->oldestReport->next;
                        buffer->isOverflow = true;
                    }

#if (DEBUG_IED_SERVER == 1)
                    printf("IED_SERVER: REMOVE report with ID ");
                    printReportId(buffer->oldestReport);
                    printf("\n");
#endif

                    buffer->oldestReport = buffer->oldestReport->next;

                    buffer->reportsCount--;

                    if (buffer->oldestReport == NULL) {
                        buffer->oldestReport = (ReportBufferEntry*) entryBufPos;
                        buffer->oldestReport->next = NULL;
                        break;
                    }
                }
            }

            buffer->lastEnqueuedReport->next = (ReportBufferEntry*) entryBufPos;
        }
        else if (buffer->lastEnqueuedReport < buffer->oldestReport) {
            entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);

            if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
                entryBufPos = buffer->memoryBlock;

                /* remove older reports in upper buffer part */
                while ((uint8_t*) buffer->oldestReport > buffer->memoryBlock) {
                    assert(buffer->oldestReport != NULL);

                    if (buffer->nextToTransmit == buffer->oldestReport) {
                        buffer->nextToTransmit = buffer->oldestReport->next;
                        buffer->isOverflow = true;
                    }

#if (DEBUG_IED_SERVER == 1)
                    printf("IED_SERVER: REMOVE report with ID ");
                    printReportId(buffer->oldestReport);
                    printf("\n");
#endif

                    buffer->oldestReport = buffer->oldestReport->next;
                    buffer->reportsCount--;
                }

                /* remove older reports in lower buffer part that will be overwritten by new report */
                while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) {
                    if (buffer->oldestReport == NULL)
                        break;

                    assert(buffer->oldestReport != NULL);

                    if (buffer->nextToTransmit == buffer->oldestReport) {
                        buffer->nextToTransmit = buffer->oldestReport->next;
                        buffer->isOverflow = true;
                    }

#if (DEBUG_IED_SERVER == 1)
                    printf("IED_SERVER: REMOVE report with ID ");
                    printReportId(buffer->oldestReport);
                    printf("\n");
#endif

                    buffer->oldestReport = buffer->oldestReport->next;
                    buffer->reportsCount--;
                }
            }
            else {
                while (((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) && ((uint8_t*) buffer->oldestReport != buffer->memoryBlock)) {

                    if (buffer->oldestReport == NULL)
                        break;

                    assert(buffer->oldestReport != NULL);

                    if (buffer->nextToTransmit == buffer->oldestReport) {
                        buffer->nextToTransmit = buffer->oldestReport->next;
                        buffer->isOverflow = true;
                    }

#if (DEBUG_IED_SERVER == 1)
                    printf("IED_SERVER: REMOVE report with ID ");
                    printReportId(buffer->oldestReport);
                    printf("\n");
#endif

                    buffer->oldestReport = buffer->oldestReport->next;
                    buffer->reportsCount--;
                }
            }

            buffer->lastEnqueuedReport->next = (ReportBufferEntry*) entryBufPos;
        }

    }

    entryStartPos = entryBufPos;
    buffer->lastEnqueuedReport = (ReportBufferEntry*) entryBufPos;
    buffer->lastEnqueuedReport->next = NULL;
    buffer->reportsCount++;

    ReportBufferEntry* entry = (ReportBufferEntry*) entryBufPos;

    /* ENTRY_ID is set to system time in ms! */
    uint64_t entryId = timeOfEntry;

    if (entryId <= reportControl->lastEntryId)
    	entryId = reportControl->lastEntryId + 1;

    entry->timeOfEntry = entryId;

#if (ORDER_LITTLE_ENDIAN == 1)
    memcpyReverseByteOrder(entry->entryId, (uint8_t*) &entryId, 8);
#else
    memcpy (entry->entryId, (uint8_t*) &entryId, 8);
#endif

#if (DEBUG_IED_SERVER == 1)
    printf("IED_SERVER: ENCODE REPORT WITH ID: ");
    printReportId(entry);
    printf(" at pos %p\n", entryStartPos);
#endif

    if (reportControl->enabled == false) {
        MmsValue* entryIdValue = MmsValue_getElement(reportControl->rcbValues, 11);
        MmsValue_setOctetString(entryIdValue, (uint8_t*) entry->entryId, 8);
    }

    if (isIntegrity)
        entry->flags = 1;
    else if (isGI)
        entry->flags = 2;
    else
        entry->flags = 0;

    if ((bufferEntrySize % sizeof(void*)) > 0)
       bufferEntrySize  = sizeof(void*) * ((bufferEntrySize  + sizeof(void*) - 1) / sizeof(void*));

    entry->entryLength = bufferEntrySize;

    entryBufPos += sizeof(ReportBufferEntry);

    if (isIntegrity || isGI) {
        DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas;

        int i;

        for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) {

            assert(dataSetEntry != NULL);

            *entryBufPos = (uint8_t) reportControl->inclusionFlags[i];
            entryBufPos++;

            entryBufPos = MmsValue_cloneToBuffer(dataSetEntry->value, entryBufPos);

            dataSetEntry = dataSetEntry->sibling;
        }

    }
    else {
        inclusionFieldStatic.value.bitString.buf = entryBufPos;
        memset(entryBufPos, 0, inclusionFieldSize);
        entryBufPos += inclusionFieldSize;

        int i;

        for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) {

            if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) {

                assert(reportControl->bufferedDataSetValues[i] != NULL);

                *entryBufPos = (uint8_t) reportControl->inclusionFlags[i];
                entryBufPos++;

                entryBufPos = MmsValue_cloneToBuffer(reportControl->bufferedDataSetValues[i], entryBufPos);

                MmsValue_setBitStringBit(inclusionField, i, true);
            }

        }
    }

    /* clear inclusion flags */
    int i;

    for (i = 0; i < reportControl->dataSet->elementCount; i++)
        reportControl->inclusionFlags[i] = REPORT_CONTROL_NONE;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: enqueueReport: encoded %i bytes for report (estimated %i) at buffer offset %i\n",
            (int) (entryBufPos - entryStartPos), bufferEntrySize, (int) (entryStartPos - buffer->memoryBlock));

#if (DEBUG_IED_SERVER == 1)
    printEnqueuedReports(reportControl);
#endif

    if (buffer->nextToTransmit == NULL)
        buffer->nextToTransmit = buffer->lastEnqueuedReport;

    if (buffer->oldestReport == NULL)
        buffer->oldestReport = buffer->lastEnqueuedReport;

    reportControl->lastEntryId = entryId;

exit_function:
    return;
} /* enqueuReport() */

static void
sendNextReportEntry(ReportControl* self)
{
#define LOCAL_STORAGE_MEMORY_SIZE 65536

    if (self->reportBuffer->nextToTransmit == NULL)
        return;

    char* localStorage = (char*) GLOBAL_MALLOC(LOCAL_STORAGE_MEMORY_SIZE); /* reserve 64k for dynamic memory allocation -
                                     this can be optimized - maybe there is a good guess for the
                                     required memory size */

    if (localStorage == NULL) /* out-of-memory */
        goto return_out_of_memory;

    MemoryAllocator ma;
    MemoryAllocator_init(&ma, localStorage, LOCAL_STORAGE_MEMORY_SIZE);

    ReportBufferEntry* report = self->reportBuffer->nextToTransmit;

#if (DEBUG_IED_SERVER == 1)
    printf("IED_SERVER: SEND NEXT REPORT: ");
    printReportId(report);
    printf(" size: %i\n", report->entryLength);
#endif

    MmsValue* entryIdValue = MmsValue_getElement(self->rcbValues, 11);
    MmsValue_setOctetString(entryIdValue, (uint8_t*) report->entryId, 8);

    MemAllocLinkedList reportElements = MemAllocLinkedList_create(&ma);

    assert(reportElements != NULL);

    if (reportElements == NULL)
        goto return_out_of_memory;

    MmsValue* rptId = ReportControl_getRCBValue(self, "RptID");
    MmsValue* optFlds = ReportControl_getRCBValue(self, "OptFlds");

    if (MemAllocLinkedList_add(reportElements, rptId) == NULL)
        goto return_out_of_memory;

    if (MemAllocLinkedList_add(reportElements, optFlds) == NULL)
        goto return_out_of_memory;

    MmsValue inclusionFieldStack;

    inclusionFieldStack.type = MMS_BIT_STRING;
    inclusionFieldStack.value.bitString.size = MmsValue_getBitStringSize(self->inclusionField);

    uint8_t* currentReportBufferPos = (uint8_t*) report + sizeof(ReportBufferEntry);

    inclusionFieldStack.value.bitString.buf = currentReportBufferPos;

    MmsValue* inclusionField = &inclusionFieldStack;

    if (report->flags == 0)
        currentReportBufferPos += MmsValue_getBitStringByteSize(inclusionField);
    else {
        inclusionFieldStack.value.bitString.buf =
                (uint8_t*) MemoryAllocator_allocate(&ma, MmsValue_getBitStringByteSize(inclusionField));

        if (inclusionFieldStack.value.bitString.buf == NULL)
            goto return_out_of_memory;
    }

    uint8_t* valuesInReportBuffer = currentReportBufferPos;

    MmsValue_setBitStringBit(optFlds, 9, false); /* segmentation */

    MmsValue* sqNum = ReportControl_getRCBValue(self, "SqNum");

    if (MmsValue_getBitStringBit(optFlds, 1)) /* sequence number */
        if (MemAllocLinkedList_add(reportElements, sqNum) == NULL)
            goto return_out_of_memory;

    if (MmsValue_getBitStringBit(optFlds, 2)) { /* report time stamp */
    	MmsValue* timeOfEntry = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

    	if (timeOfEntry == NULL) goto return_out_of_memory;

    	timeOfEntry->deleteValue = 0;
    	timeOfEntry->type = MMS_BINARY_TIME;
    	timeOfEntry->value.binaryTime.size = 6;

    	MmsValue_setBinaryTime(timeOfEntry, report->timeOfEntry);

        if (MemAllocLinkedList_add(reportElements, timeOfEntry) == NULL)
            goto return_out_of_memory;
    }

    if (MmsValue_getBitStringBit(optFlds, 4)) {/* data set reference */
        MmsValue* datSet = ReportControl_getRCBValue(self, "DatSet");
        if (MemAllocLinkedList_add(reportElements, datSet) == NULL)
            goto return_out_of_memory;
    }

    if (MmsValue_getBitStringBit(optFlds, 6)) { /* bufOvfl */

        MmsValue* bufOvfl = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

        if (bufOvfl == NULL) goto return_out_of_memory;

        bufOvfl->deleteValue = 0;
        bufOvfl->type = MMS_BOOLEAN;
        bufOvfl->value.boolean = self->reportBuffer->isOverflow;

        if (self->reportBuffer->isOverflow)
            self->reportBuffer->isOverflow = false;

        if (MemAllocLinkedList_add(reportElements, bufOvfl) == NULL)
            goto return_out_of_memory;
    }

    if (MmsValue_getBitStringBit(optFlds, 7)) { /* entryID */
        MmsValue* entryId = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

        if (entryId == NULL) goto return_out_of_memory;

        entryId->deleteValue = 0;
        entryId->type = MMS_OCTET_STRING;
        entryId->value.octetString.buf = report->entryId;
        entryId->value.octetString.size = 8;
        entryId->value.octetString.maxSize = 8;

        if (MemAllocLinkedList_add(reportElements, entryId) == NULL)
            goto return_out_of_memory;
    }

    if (MmsValue_getBitStringBit(optFlds, 8))
        if (MemAllocLinkedList_add(reportElements, self->confRev) == NULL)
            goto return_out_of_memory;

    if (report->flags > 0)
        MmsValue_setAllBitStringBits(inclusionField);

    if (MemAllocLinkedList_add(reportElements, inclusionField) == NULL)
        goto return_out_of_memory;

    /* add data references if selected */
    if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */
        DataSetEntry* dataSetEntry = self->dataSet->fcdas;

        LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent;

        IedModel* iedModel = (IedModel*) ld->parent;

        char* iedName = iedModel->name;

        int iedNameLength = strlen(iedName);

        int i = 0;

        for (i = 0; i < self->dataSet->elementCount; i++) {
          assert(dataSetEntry->value != NULL);

          bool addReferenceForEntry = false;

          if (report->flags > 0)
             addReferenceForEntry = true;
          else
              if (MmsValue_getBitStringBit(inclusionField, i))
                  addReferenceForEntry = true;

          if (addReferenceForEntry) {

              int ldNameLength =  strlen(dataSetEntry->logicalDeviceName);
              int variableNameLength = strlen(dataSetEntry->variableName);

              int refLen = iedNameLength
                      + ldNameLength
                      + variableNameLength + 1;

              char* dataReference = (char*) MemoryAllocator_allocate(&ma, refLen + 1);

              if (dataReference == NULL) goto return_out_of_memory;

              int currentPos = 0;

              int j;

              for (j = 0; j < iedNameLength; j++) {
                  dataReference[currentPos++] = iedName[j];
              }

              for (j = 0; j <  ldNameLength; j++) {
                  dataReference[currentPos] = dataSetEntry->logicalDeviceName[j];
                  currentPos++;
              }

              dataReference[currentPos++] = '/';

              for (j = 0; j < variableNameLength; j++) {
                  dataReference[currentPos++] = dataSetEntry->variableName[j];
              }

              dataReference[currentPos] = 0;

              MmsValue* dataRef = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

              if (dataRef == NULL) goto return_out_of_memory;

              dataRef->deleteValue = 0;
              dataRef->type = MMS_VISIBLE_STRING;
              dataRef->value.visibleString.buf = dataReference;
              dataRef->value.visibleString.size = refLen;


              if (MemAllocLinkedList_add(reportElements, dataRef) == NULL)
                  goto return_out_of_memory;

          }

          dataSetEntry = dataSetEntry->sibling;

        }
    }

    /* add data set value elements */
    int i = 0;
    for (i = 0; i < self->dataSet->elementCount; i++) {

        if (report->flags > 0) {
            currentReportBufferPos++;
            if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL)
                goto return_out_of_memory;

            currentReportBufferPos += MmsValue_getSizeInMemory((MmsValue*) currentReportBufferPos);
        }
        else {
            if (MmsValue_getBitStringBit(inclusionField, i)) {
                currentReportBufferPos++;
                if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL)
                    goto return_out_of_memory;
                currentReportBufferPos += MmsValue_getSizeInMemory((MmsValue*) currentReportBufferPos);
            }
        }
    }

    /* add reason code to report if requested */
    if (MmsValue_getBitStringBit(optFlds, 3)) {
        currentReportBufferPos = valuesInReportBuffer;

        for (i = 0; i < self->dataSet->elementCount; i++) {

            if (report->flags > 0) {
                MmsValue* reason = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

                if (reason == NULL) goto return_out_of_memory;

                reason->deleteValue = 0;
                reason->type = MMS_BIT_STRING;
                reason->value.bitString.size = 6;
                reason->value.bitString.buf = (uint8_t*) MemoryAllocator_allocate(&ma, 1);

                if (reason->value.bitString.buf == NULL) goto return_out_of_memory;

                MmsValue_deleteAllBitStringBits(reason);

                if (report->flags & 0x02) /* GI */
                    MmsValue_setBitStringBit(reason, 5, true);

                if (report->flags & 0x01) /* Integrity */
                    MmsValue_setBitStringBit(reason, 4, true);

                if (MemAllocLinkedList_add(reportElements, reason) == NULL)
                    goto return_out_of_memory;

                currentReportBufferPos++;

                MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos;

                currentReportBufferPos += MmsValue_getSizeInMemory(dataSetElement);
            }
            else if (MmsValue_getBitStringBit(inclusionField, i)) {
                MmsValue* reason = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue));

                if (reason == NULL) goto return_out_of_memory;

                reason->deleteValue = 0;
                reason->type = MMS_BIT_STRING;
                reason->value.bitString.size = 6;
                reason->value.bitString.buf = (uint8_t*) MemoryAllocator_allocate(&ma, 1);

                if (reason->value.bitString.buf == NULL)
                    goto return_out_of_memory;

                MmsValue_deleteAllBitStringBits(reason);

                switch((ReportInclusionFlag) *currentReportBufferPos) {
                case REPORT_CONTROL_QUALITY_CHANGED:
                    MmsValue_setBitStringBit(reason, 2, true);
                    break;
                case REPORT_CONTROL_VALUE_CHANGED:
                    MmsValue_setBitStringBit(reason, 1, true);
                    break;
                case REPORT_CONTROL_VALUE_UPDATE:
                    MmsValue_setBitStringBit(reason, 3, true);
                    break;
                default:
                    break;
                }

                currentReportBufferPos++;

                MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos;

                currentReportBufferPos += MmsValue_getSizeInMemory(dataSetElement);

                if (MemAllocLinkedList_add(reportElements, reason) == NULL)
                    goto return_out_of_memory;
            }
        }
    }

    MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", (LinkedList) reportElements, false);

    /* Increase sequence number */
    self->sqNum++;
    MmsValue_setUint32(sqNum, self->sqNum);

    assert(self->reportBuffer->nextToTransmit != self->reportBuffer->nextToTransmit->next);

    self->reportBuffer->nextToTransmit = self->reportBuffer->nextToTransmit->next;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: sendNextReportEntry: memory(used/size): %i/%i\n",
                (int) (ma.currentPtr - ma.memoryBlock), ma.size);

#if (DEBUG_IED_SERVER == 1)
    printf("IED_SERVER: reporting.c nextToTransmit: %p\n", self->reportBuffer->nextToTransmit);
    printEnqueuedReports(self);
#endif

    goto cleanup_and_return;

return_out_of_memory:

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: sendNextReportEntry failed - memory allocation problem!\n");

    //TODO set some flag to notify application here

cleanup_and_return:

    if (localStorage != NULL)
        GLOBAL_FREEMEM(localStorage);

}

void
Reporting_activateBufferedReports(MmsMapping* self)
{
    LinkedList element = self->reportControls;

    while ((element = LinkedList_getNext(element)) != NULL ) {
        ReportControl* rc = (ReportControl*) element->data;

        if (rc->buffered) {
            if (updateReportDataset(self, rc, NULL, NULL))
                rc->isBuffering = true;
            else
                rc->isBuffering = false;
        }
    }
}

static void
processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
{


    if ((rc->enabled) || (rc->isBuffering)) {

        if (rc->triggerOps & TRG_OPT_GI) {
            if (rc->gi) {

                /* send current events in event buffer before GI report */
                if (rc->triggered) {
                    if (rc->buffered)
                        enqueueReport(rc, false, false, currentTimeInMs);
                    else
                        sendReport(rc, false, false);

                    rc->triggered = false;
                }

                if (rc->buffered)
                    enqueueReport(rc, false, true, currentTimeInMs);
                else
                    sendReport(rc, false, true);

                rc->gi = false;

                rc->triggered = false;
            }
        }

        if (rc->triggerOps & TRG_OPT_INTEGRITY) {

            if (rc->intgPd > 0) {
                if (currentTimeInMs >= rc->nextIntgReportTime) {

                    /* send current events in event buffer before integrity report */
                    if (rc->triggered) {
                        if (rc->buffered)
                            enqueueReport(rc, false, false, currentTimeInMs);
                        else
                            sendReport(rc, false, false);

                        rc->triggered = false;
                    }

                    rc->nextIntgReportTime = currentTimeInMs + rc->intgPd;

                    if (rc->buffered)
                        enqueueReport(rc, true, false, currentTimeInMs);
                    else
                        sendReport(rc, true, false);

                    rc->triggered = false;
                }
            }
        }

        if (rc->triggered) {
            if (currentTimeInMs >= rc->reportTime) {

                if (rc->buffered)
                    enqueueReport(rc, false, false, currentTimeInMs);
                else
                    sendReport(rc, false, false);

                rc->triggered = false;
            }
        }

        if (rc->buffered && rc->enabled)
            sendNextReportEntry(rc);
    }


}

void
Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs)
{
    LinkedList element = self->reportControls;

    while ((element = LinkedList_getNext(element)) != NULL ) {
        ReportControl* rc = (ReportControl*) element->data;

        ReportControl_lockNotify(rc);

        processEventsForReport(rc, currentTimeInMs);

        ReportControl_unlockNotify(rc);
    }
}

void
ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag)
{
    ReportControl_lockNotify(self);

    if (self->inclusionFlags[dataSetEntryIndex] != 0) { /* report for this data set entry is already pending (bypass BufTm) */
        self->reportTime = Hal_getTimeInMs();
        processEventsForReport(self, self->reportTime);
    }

    self->inclusionFlags[dataSetEntryIndex] = flag;

    /* buffer value for report */
    if (self->bufferedDataSetValues[dataSetEntryIndex] == NULL)
        self->bufferedDataSetValues[dataSetEntryIndex] = MmsValue_clone(self->valueReferences[dataSetEntryIndex]);
    else
        MmsValue_update(self->bufferedDataSetValues[dataSetEntryIndex], self->valueReferences[dataSetEntryIndex]);

    if (self->triggered == false) {
        uint64_t currentTime = Hal_getTimeInMs();

        MmsValue_setBinaryTime(self->timeOfEntry, currentTime);

        self->reportTime = currentTime + self->bufTm;
    }

    self->triggered = true;

    ReportControl_unlockNotify(self);
}

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