/*
 *  control.c
 *
 *  Copyright 2013-2022 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 "control.h"

#include "mms_mapping.h"
#include "iec61850_server.h"
#include "ied_server_private.h"
#include "mms_value_internal.h"
#include <stdio.h>

#if (CONFIG_IEC61850_CONTROL_SERVICE == 1)

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

#ifndef DEBUG_IED_SERVER
#define DEBUG_IED_SERVER 0
#endif

#define STATE_UNSELECTED 0               /* idle state for SBO controls */
#define STATE_READY 1                    /* idle state for direct controls, or selected state for SBO controls */
#define STATE_WAIT_FOR_ACTIVATION_TIME 2 /* time activated control is waiting for execution time */
#define STATE_PERFORM_TEST 3             /* waiting for application to perform tests */
#define STATE_WAIT_FOR_EXECUTION 4       /* control is scheduled and waiting for execution */
#define STATE_OPERATE 5                  /* waiting for application to execute the command */
#define STATE_WAIT_FOR_SELECT 6          /* waiting for application to perform/confirm selection */

#define PENDING_EVENT_SELECTED 1
#define PENDING_EVENT_UNSELECTED 2
#define PENDING_EVENT_OP_RCVD_TRUE 4
#define PENDING_EVENT_OP_RCVD_FALSE 8
#define PENDING_EVENT_OP_OK_TRUE 16
#define PENDING_EVENT_OP_OK_FALSE 32

static MmsValue emptyString = {MMS_STRUCTURE, false, {DATA_ACCESS_ERROR_OBJECT_INVALIDATED}};

static MmsValue delayedResponse = {MMS_DATA_ACCESS_ERROR, false, {DATA_ACCESS_ERROR_NO_RESPONSE}};

void
ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connection, char* ctlVariable, int error,
        ControlAddCause addCause, MmsValue* ctlNum, MmsValue* origin, bool handlerMode);

void
ControlObject_sendCommandTerminationPositive(ControlObject* self);

void
ControlObject_sendCommandTerminationNegative(ControlObject* self);

bool
ControlObject_unselect(ControlObject* self, MmsServerConnection connection, MmsMapping* mmsMapping);

static MmsValue*
getOperParameterCtlNum(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            return MmsValue_getElement(operParameters, 3);
        else if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 2);
    }

    return NULL;
}

static MmsValue*
getCancelParameterCtlNum(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 3);
        else if (MmsValue_getArraySize(operParameters) == 5)
            return MmsValue_getElement(operParameters, 2);
    }

    return NULL;
}

static MmsValue*
getCancelParameterOrigin(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 2);
        else if (MmsValue_getArraySize(operParameters) == 5)
            return MmsValue_getElement(operParameters, 1);
    }

    return NULL;
}

static MmsValue*
getOperParameterTest(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            return MmsValue_getElement(operParameters, 5);
        else if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 4);
    }

    return NULL;
}

static MmsValue*
getOperParameterCheck(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            return MmsValue_getElement(operParameters, 6);
        else if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 5);
    }

    return NULL;
}

static MmsValue*
getOperParameterOrigin(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            return MmsValue_getElement(operParameters, 2);
        else if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 1);
    }

    return NULL;
}

static MmsValue*
getCancelParameterTest(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 6)
            return MmsValue_getElement(operParameters, 5);
        else if (MmsValue_getArraySize(operParameters) == 5)
            return MmsValue_getElement(operParameters, 4);
    }

    return NULL;
}

/* access the MmsValue of Oper.T or SBOw.T */
static MmsValue*
getOperParameterTime(MmsValue* operParameters)
{
    MmsValue* timeParameter = NULL;

    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            timeParameter = MmsValue_getElement(operParameters, 4);
        else if (MmsValue_getArraySize(operParameters) == 6)
            timeParameter = MmsValue_getElement(operParameters, 3);
    }

    if (timeParameter)
        if ((MmsValue_getType(timeParameter) == MMS_UTC_TIME) || (MmsValue_getType(timeParameter) == MMS_BINARY_TIME))
            return timeParameter;

    return NULL;
}

static MmsValue*
getCancelParameterTime(MmsValue* operParameters)
{
    MmsValue* timeParameter = NULL;

    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 6)
            timeParameter = MmsValue_getElement(operParameters, 4);
        else if (MmsValue_getArraySize(operParameters) == 5)
            timeParameter = MmsValue_getElement(operParameters, 3);
    }

    if (timeParameter != NULL)
        if ((MmsValue_getType(timeParameter) == MMS_UTC_TIME) || (MmsValue_getType(timeParameter) == MMS_BINARY_TIME))
            return timeParameter;

    return NULL;
}

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)

static void
copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject, IEC61850_ServiceType serviceType)
{
    if (controlObject->ctlVal)
    {
        ControlTrkInstance trkInst = NULL;

        switch (controlObject->cdc)
        {
        case CST_SPCTRK:
            trkInst = self->spcTrk;
            break;
        case CST_DPCTRK:
            trkInst = self->dpcTrk;
            break;
        case CST_INCTRK:
            trkInst = self->incTrk;
            break;
        case CST_ENCTRK1:
            trkInst = self->encTrk1;
            break;
        case CST_APCFTRK:
            trkInst = self->apcFTrk;
            break;
        case CST_APCINTTRK:
            trkInst = self->apcIntTrk;
            break;
        case CST_BSCTRK:
            trkInst = self->bscTrk;
            break;
        case CST_ISCTRK:
            trkInst = self->iscTrk;
            break;
        case CST_BACTRK:
            trkInst = self->bacTrk;
            break;
        default:
            break;
        }

        if (trkInst)
        {
            if (trkInst->ctlVal)
                MmsValue_update(trkInst->ctlVal->mmsValue, controlObject->ctlVal);

            if (trkInst->origin)
                MmsValue_update(trkInst->origin->mmsValue, controlObject->origin);

            if (trkInst->ctlNum)
                MmsValue_update(trkInst->ctlNum->mmsValue, controlObject->ctlNum);

            if (trkInst->operTm)
                MmsValue_setUtcTimeMsEx(trkInst->operTm->mmsValue, controlObject->operateTime, self->iedServer->timeQuality);

            if (trkInst->respAddCause)
                MmsValue_update(trkInst->respAddCause->mmsValue, controlObject->addCause);

            MmsValue* operVal = NULL;

            if (serviceType == IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES)
            {
                if (controlObject->sbow)
                    operVal = controlObject->sbow;
            }
            else if (serviceType == IEC61850_SERVICE_TYPE_OPERATE)
            {
                if (controlObject->oper)
                    operVal = controlObject->oper;
            }
            else if (serviceType == IEC61850_SERVICE_TYPE_CANCEL)
            {
                if (controlObject->cancel)
                {
                    operVal = controlObject->cancel;
                    if (trkInst->Test) {
                        MmsValue_update(trkInst->Test->mmsValue, getCancelParameterTest(operVal));
                    }

                    if (trkInst->T) {
                        MmsValue_update(trkInst->T->mmsValue, getCancelParameterTime(operVal));
                    }
                }
            }

            if (operVal)
            {
                if (trkInst->Test) {
                    MmsValue_update(trkInst->Test->mmsValue, getOperParameterTest(operVal));
                }

                if (trkInst->Check) {
                    MmsValue_update(trkInst->Check->mmsValue, getOperParameterCheck(operVal));
                }

                if (trkInst->T) {
                    MmsValue_update(trkInst->T->mmsValue, getOperParameterTime(operVal));
                }
            }
        }
    }
}

static IEC61850_ServiceError
convertCheckHandlerResultToServiceError(CheckHandlerResult controlHandlerResult)
{
    IEC61850_ServiceError serviceError;

    switch (controlHandlerResult)
    {
    case CONTROL_HARDWARE_FAULT:
        serviceError = IEC61850_SERVICE_ERROR_FAILED_DUE_TO_SERVER_CONSTRAINT;
        break;

    case CONTROL_TEMPORARILY_UNAVAILABLE:
        serviceError = IEC61850_SERVICE_ERROR_INSTANCE_IN_USE;
        break;

    case CONTROL_OBJECT_ACCESS_DENIED:
        serviceError = IEC61850_SERVICE_ERROR_ACCESS_VIOLATION;
        break;

    case CONTROL_OBJECT_UNDEFINED:
        serviceError = IEC61850_SERVICE_ERROR_INSTANCE_NOT_AVAILABLE;
        break;

    case CONTROL_VALUE_INVALID:
        serviceError = IEC61850_SERVICE_ERROR_PARAMETER_VALUE_INCONSISTENT;
        break;

    case CONTROL_WAITING_FOR_SELECT:
    case CONTROL_ACCEPTED:
        serviceError = IEC61850_SERVICE_ERROR_NO_ERROR;
        break;

    default:
        serviceError = DATA_ACCESS_ERROR_UNKNOWN;
        break;
    }

    return serviceError;
}

static void
updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject, IEC61850_ServiceType serviceType, IEC61850_ServiceError errVal)
{
    copyControlValuesToTrackingObject(self, controlObject, serviceType);

    ServiceTrkInstance trkInst = NULL;

    if (controlObject->ctlVal)
    {
        switch(controlObject->cdc)
        {
            case CST_SPCTRK:
                trkInst = (ServiceTrkInstance) self->spcTrk;
                break;
            case CST_DPCTRK:
                trkInst = (ServiceTrkInstance) self->dpcTrk;
                break;
            case CST_INCTRK:
                trkInst = (ServiceTrkInstance) self->incTrk;
                break;
            case CST_ENCTRK1:
                trkInst = (ServiceTrkInstance) self->encTrk1;
                break;
            case CST_APCFTRK:
                trkInst = (ServiceTrkInstance) self->apcFTrk;
                break;
            case CST_APCINTTRK:
                trkInst = (ServiceTrkInstance) self->apcIntTrk;
                break;
            case CST_BSCTRK:
                trkInst = (ServiceTrkInstance) self->bscTrk;
                break;
            case CST_ISCTRK:
                trkInst = (ServiceTrkInstance) self->iscTrk;
                break;
            case CST_BACTRK:
                trkInst = (ServiceTrkInstance) self->bacTrk;
                break;
            default:
                if (DEBUG_IED_SERVER)
                    printf("IED_SERVER: could not determine servicetracking type\n");
                break;
        }
    }

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

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

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

        char objRef[129];

        /* create object reference */
        char* ldName = controlObject->mmsDomain->domainName;
        char* lnName = controlObject->lnName;

        snprintf(objRef, 129, "%s/%s.%s", ldName, lnName, controlObject->name);

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

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

static void
unselectObject(ControlObject* self, SelectStateChangedReason reason, MmsMapping* mmsMapping);

static void
updateNextControlTimeout(MmsMapping* self, uint64_t timeout)
{
    if (timeout < self->nextControlTimeout)
        self->nextControlTimeout = timeout;
}

static void
setState(ControlObject* self, int newState)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_wait(self->stateLock);
#endif

    self->state = newState;

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

static int
getState(ControlObject* self)
{
    int state;

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

    state = self->state;

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

    return state;
}

static void
setStSeld(ControlObject* self, bool value)
{
    if (self->stSeld)
    {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_wait(self->pendingEventsLock);
#endif

        if (value)
            self->pendingEvents |= PENDING_EVENT_SELECTED;
        else
            self->pendingEvents |= PENDING_EVENT_UNSELECTED;

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

static void
updateSboTimeoutValue(ControlObject* self)
{
    if (self->sboTimeout != NULL)
    {
        uint32_t sboTimeoutVal = MmsValue_toInt32(self->sboTimeout);

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: set timeout for %s/%s.%s to %u\n", MmsDomain_getName(self->mmsDomain), self->lnName, self->name, sboTimeoutVal);

        self->selectTimeout = sboTimeoutVal;
    }
    else
        self->selectTimeout = CONFIG_CONTROL_DEFAULT_SBO_TIMEOUT;
}

static void
selectObject(ControlObject* self, uint64_t selectTime, MmsServerConnection connection, MmsMapping* mmsMapping)
{
    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: control %s/%s.%s selected\n", MmsDomain_getName(self->mmsDomain), self->lnName, self->name);

    updateSboTimeoutValue(self);

    self->selectTime = selectTime;
    self->mmsConnection = connection;
    setStSeld(self, true);
    setState(self, STATE_READY);

    updateNextControlTimeout(mmsMapping, selectTime);

    if (self->selectStateChangedHandler)
    {
        self->selectStateChangedHandler((ControlAction) self,
                self->selectStateChangedHandlerParameter,
                true,
                SELECT_STATE_REASON_SELECTED);
    }
}

static void
unselectObject(ControlObject* self, SelectStateChangedReason reason, MmsMapping* mmsMapping)
{
    if (getState(self) != STATE_UNSELECTED)
    {
        setState(self, STATE_UNSELECTED);

        setStSeld(self, false);

        /* trigger timeout check in next cycle to update the next timeout value */
        mmsMapping->nextControlTimeout = 0;

        if (self->selectStateChangedHandler)
        {
            self->selectStateChangedHandler((ControlAction) self,
                    self->selectStateChangedHandlerParameter,
                    false,
                    reason);
        }

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: control %s/%s.%s unselected\n", MmsDomain_getName(self->mmsDomain), self->lnName, self->name);
    }
}

static void
checkSelectTimeout(ControlObject* self, uint64_t currentTime, MmsMapping* mmsMapping)
{
    if ((self->ctlModel == 2) || (self->ctlModel == 4))
    {
        if (getState(self) == STATE_READY)
        {
            if (self->selectTimeout > 0)
            {
                if (currentTime > (self->selectTime + self->selectTimeout))
                {
                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: select-timeout (timeout-val = %u) for control %s/%s.%s\n",
                                self->selectTimeout, MmsDomain_getName(self->mmsDomain), self->lnName, self->name);

                    unselectObject(self, SELECT_STATE_REASON_TIMEOUT, mmsMapping);
                }
                else {
                    updateNextControlTimeout(mmsMapping, self->selectTime + self->selectTimeout);
                }
            }
        }
    }
}

static void
setOpRcvd(ControlObject* self, bool value)
{
    if (self->opRcvd)
    {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_wait(self->pendingEventsLock);
#endif

        if (value)
            self->pendingEvents |= PENDING_EVENT_OP_RCVD_TRUE;
        else
            self->pendingEvents |= PENDING_EVENT_OP_RCVD_FALSE;

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

static void
setOpOk(ControlObject* self, bool value, uint64_t currentTimeInMs)
{
    if (self->opOk)
    {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_wait(self->pendingEventsLock);
#endif

        if (value)
        {
            if (self->tOpOk)
            {
                MmsValue* timestamp = self->tOpOk->mmsValue;

                MmsValue_setUtcTimeMsEx(timestamp, currentTimeInMs, self->iedServer->timeQuality);
            }

            self->pendingEvents |= PENDING_EVENT_OP_OK_TRUE;
        }
        else
            self->pendingEvents |= PENDING_EVENT_OP_OK_FALSE;

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

static bool
isSboClassOperateOnce(ControlObject* self)
{
    if (self->sboClass != NULL)
    {
        if (MmsValue_toInt32(self->sboClass) == 1)
            return false;
        else
            return true;
    }
    else
        return true; /* default is operate-once ! */
}

static MmsValue*
getOperParameterOperTime(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) == 7)
            return MmsValue_getElement(operParameters, 1);
    }

    return NULL;
}

static bool
initiateControlTask(ControlObject* self)
{
    ClientConnection iedClientConnection =
            private_IedServer_getClientConnectionByHandle(self->iedServer, self->mmsConnection);

    if (iedClientConnection == NULL)
        return false;

    private_ClientConnection_increaseTasksCount(iedClientConnection);

    return true;
}

static void
exitControlTask(ControlObject* self)
{
    ClientConnection iedClientConnection =
            private_IedServer_getClientConnectionByHandle(self->iedServer, self->mmsConnection);

    if (iedClientConnection == NULL)
        return;

    /* synchronize with connection management */
    private_ClientConnection_decreaseTasksCount(iedClientConnection);
}

static void
abortControlOperation(ControlObject* self, bool unconditional, SelectStateChangedReason reason, MmsMapping* mmsMapping)
{
    if ((self->ctlModel == 2) || (self->ctlModel == 4))
    {
        if (unconditional)
        {
            unselectObject(self, reason, mmsMapping);
        }
        else
        {
            if (isSboClassOperateOnce(self))
                unselectObject(self, reason, mmsMapping);
            else
                setState(self, STATE_READY);
        }
    }
    else
        setState(self, STATE_READY);

    exitControlTask(self);
}

static ControlHandlerResult
operateControl(ControlObject* self, MmsValue* value, uint64_t currentTime, bool testCondition)
{
    self->selectTime = currentTime;

    self->errorValue = CONTROL_ERROR_NO_ERROR;
    self->addCauseValue = ADD_CAUSE_UNKNOWN;

    if (self->operateHandler != NULL)
        return self->operateHandler((ControlAction) self, self->operateHandlerParameter, value, testCondition);

    return CONTROL_RESULT_OK;
}

static void
resetAddCause(ControlObject* self)
{
    if (self)
    {
        self->addCauseValue = ADD_CAUSE_UNKNOWN;

        MmsValue_setInt32(self->addCause, self->addCauseValue);
    }
}

static void
executeControlTask(MmsMapping* self, ControlObject* controlObject, uint64_t currentTimeInMs)
{
    int state;

executeStateMachine:

    state = getState(controlObject);

    switch (state)
    {
    case STATE_WAIT_FOR_SELECT:
    {
        controlObject->isSelect = 1;

        controlObject->errorValue = 0;
        controlObject->addCauseValue = ADD_CAUSE_SELECT_FAILED;

        CheckHandlerResult checkHandlerResult = controlObject->checkHandler((ControlAction) controlObject, controlObject->checkHandlerParameter,
                controlObject->ctlVal, controlObject->testMode, controlObject->interlockCheck);

        controlObject->isSelect = 0;

        if (checkHandlerResult != CONTROL_WAITING_FOR_SELECT)
        {
            if (controlObject->ctlModel == 2)
            {
                LinkedList values = LinkedList_create();

                if (checkHandlerResult == CONTROL_ACCEPTED)
                {
                    LinkedList_add(values, controlObject->sbo);

                    selectObject(controlObject, Hal_getTimeInMs(), controlObject->mmsConnection, self);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                    updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
                }
                else
                {
                    LinkedList_add(values, &emptyString);

                    setState(controlObject, STATE_UNSELECTED);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                    updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT,
                            convertCheckHandlerResultToServiceError(checkHandlerResult));
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
                }

                MmsServerConnection_sendReadResponse(controlObject->mmsConnection, controlObject->operateInvokeId, values, false);

                LinkedList_destroyStatic(values);
            }
            else if (controlObject->ctlModel == 4)
            {
                if (checkHandlerResult == CONTROL_ACCEPTED)
                {
                    selectObject(controlObject, Hal_getTimeInMs(), controlObject->mmsConnection, self);

                    if (controlObject->ctlNumSt)
                        MmsValue_update(controlObject->ctlNumSt, controlObject->ctlNum);

                    if (controlObject->originSt)
                        MmsValue_update(controlObject->originSt, controlObject->origin);

                    MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId, DATA_ACCESS_ERROR_SUCCESS, false);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                    updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: SBOw - selected successful\n");
                }
                else
                {
                    setState(controlObject, STATE_UNSELECTED);

                    ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "SBOw",
                            controlObject->errorValue, controlObject->addCauseValue, controlObject->ctlNum, controlObject->origin, false);

                    MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId, (MmsDataAccessError) checkHandlerResult, false);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                    updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES,
                            convertCheckHandlerResultToServiceError(checkHandlerResult));
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

                    resetAddCause(controlObject);

                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: SBOw - select rejected by application!\n");
                }
            }
            else
            {
                /* ERROR: invalid internal state! */
                setState(controlObject, STATE_WAIT_FOR_SELECT);
            }

        }
        else
        {
            updateNextControlTimeout(self, Hal_getTimeInMs() + 100);
        }

    }
    break;

    case STATE_WAIT_FOR_ACTIVATION_TIME:
    case STATE_WAIT_FOR_EXECUTION:
    {
        ControlHandlerResult dynamicCheckResult = CONTROL_RESULT_OK;
        bool isTimeActivatedControl = false;

        if (state == STATE_WAIT_FOR_ACTIVATION_TIME)
           isTimeActivatedControl = true;

        controlObject->errorValue = CONTROL_ERROR_NO_ERROR;
        controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK;

        if (controlObject->waitForExecutionHandler != NULL)
        {
            dynamicCheckResult = controlObject->waitForExecutionHandler((ControlAction) controlObject, controlObject->waitForExecutionHandlerParameter, controlObject->ctlVal,
                    controlObject->testMode, controlObject->synchroCheck);
        }

        if (dynamicCheckResult == CONTROL_RESULT_FAILED)
        {
            if ((controlObject->errorValue != CONTROL_ERROR_NO_ERROR) || (controlObject->addCauseValue != ADD_CAUSE_UNKNOWN))
            {
                ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "Oper",
                        controlObject->errorValue, controlObject->addCauseValue,
                        controlObject->ctlNum, controlObject->origin, false);
            }

            if (!isTimeActivatedControl)
            {
                MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId,
                        DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED, true);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_OPERATE, IEC61850_SERVICE_ERROR_ACCESS_NOT_ALLOWED_IN_CURRENT_STATE);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
            }
            else
            {
#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_TIME_ACTIVATED_OPERATE, IEC61850_SERVICE_ERROR_ACCESS_NOT_ALLOWED_IN_CURRENT_STATE);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
            }

            resetAddCause(controlObject);

            abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self);
            exitControlTask(controlObject);
        }
        else if (dynamicCheckResult == CONTROL_RESULT_OK)
        {
            if (isTimeActivatedControl)
            {
                ControlObject_sendCommandTerminationPositive(controlObject);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_COMMAND_TERMINATION, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

                MmsValue* operTm = getOperParameterOperTime(controlObject->oper);

                MmsValue_setUtcTime(operTm, 0);
                MmsValue_setUtcTimeQuality(operTm, self->iedServer->timeQuality);
            }
            else
            {
                MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId,
                        DATA_ACCESS_ERROR_SUCCESS, true);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_OPERATE, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
            }

            setState(controlObject, STATE_OPERATE);

            setOpOk(controlObject, true, currentTimeInMs);

            goto executeStateMachine;
        }
        else
        {
            updateNextControlTimeout(self, Hal_getTimeInMs() + 10);
        }
    }
    break;

    case STATE_OPERATE:
    {
        uint64_t currentTime = Hal_getTimeInMs();

        ControlHandlerResult result = operateControl(controlObject, controlObject->ctlVal, currentTime, controlObject->testMode);

        if (result != CONTROL_RESULT_WAITING)
        {
            if (result == CONTROL_RESULT_OK)
            {
                if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3))
                {
                    ControlObject_sendCommandTerminationPositive(controlObject);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                    updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_COMMAND_TERMINATION, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
                }

                abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATED, self);
            }
            else
            {
                if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3))
                {
                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: operate failed!\n");

                    ControlObject_sendCommandTerminationNegative(controlObject);
                }

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_COMMAND_TERMINATION, IEC61850_SERVICE_ERROR_FAILED_DUE_TO_SERVER_CONSTRAINT);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

                abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self);
            }

            exitControlTask(controlObject);

            setOpOk(controlObject, false, currentTimeInMs);

            resetAddCause(controlObject);
        }
        else
        {
            updateNextControlTimeout(self, currentTimeInMs + 10);
        }
    }
    break;

    }
}

ControlObject*
ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name, MmsVariableSpecification* operSpec)
{
    ControlObject* self = (ControlObject*) GLOBAL_CALLOC(1, sizeof(ControlObject));

    if (self == NULL)
        goto exit_function;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: create control object for LD: %s, LN: %s, name: %s\n", domain->domainName, lnName, name);

#if (CONFIG_MMS_THREADLESS_STACK != 1)
    self->stateLock = Semaphore_create(1);
    self->pendingEventsLock = Semaphore_create(1);

    if ((self->stateLock == NULL) || (self->pendingEventsLock == NULL))
    {
        ControlObject_destroy(self);
        self = NULL;
        goto exit_function;
    }
#endif

    self->name = StringUtils_copyString(name);

    if (self->name == NULL)
    {
        ControlObject_destroy(self);
        self = NULL;
        goto exit_function;
    }

    self->lnName = lnName;
    self->mmsDomain = domain;
    self->iedServer = iedServer;

    MmsVariableSpecification* ctlValSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "ctlVal", NULL);

    if (ctlValSpec)
    {
        self->ctlVal = MmsValue_newDefaultValue(ctlValSpec);
    }
    else
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: control object %s/%s.%s has no ctlVal element!\n", domain->domainName, lnName, name);
    }

    MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL);

    if (originSpec)
    {
        self->origin = MmsValue_newDefaultValue(originSpec);
    }
    else
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: control object %s/%s.%s has no origin element!\n", domain->domainName, lnName, name);
    }

    self->ctlNum = MmsValue_newUnsigned(8);

exit_function:
    return self;
}

void
ControlObject_initialize(ControlObject* self)
{
    MmsServer mmsServer = IedServer_getMmsServer(self->iedServer);

    if (emptyString.type != MMS_VISIBLE_STRING) {
        emptyString.type = MMS_VISIBLE_STRING;
        emptyString.value.visibleString.size = 0;
        emptyString.value.visibleString.buf = NULL;
    }

    char strBuf[130];

    char* ctlModelName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$CF$", self->name, "$ctlModel");

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: initialize control for %s\n", ctlModelName);

    MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer,
            self->mmsDomain, ctlModelName);

    if (ctlModel == NULL)
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: No control model found for variable %s\n", ctlModelName);
    }

    char* sboClassName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$CF$", self->name, "$sboClass");

    self->sboClass = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, sboClassName);

    char* ctlNumName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$ST$", self->name, "$ctlNum");

    self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName);

    if (self->ctlNumSt == NULL)
    {
        /* for APC */
        ctlNumName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$MX$", self->name, "$ctlNum");

        self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName);
    }

    char* originName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$ST$", self->name, "$origin");

    self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName);

    if (self->originSt == NULL)
    {
        /* for APC */
        originName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$MX$", self->name, "$origin");

        self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName);
    }

    char* sboTimeoutName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$CF$", self->name, "$sboTimeout");

    self->sboTimeout = MmsServer_getValueFromCache(mmsServer,
         self->mmsDomain, sboTimeoutName);

    updateSboTimeoutValue(self);

    if (self->sbo)
    {
        char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 130, 5, self->mmsDomain->domainName,
                "/", self->lnName, "$CO$", self->name);

        MmsValue_setVisibleString(self->sbo, controlObjectReference);
    }

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)

    /* determine CDC of control object for service tracking */

    char* daName = NULL;
    DataAttribute* da = NULL;

    DataAttributeType ctlValType = IEC61850_UNKNOWN_TYPE;
    DataAttributeType stValType = IEC61850_UNKNOWN_TYPE;
    DataAttributeType mxValType = IEC61850_UNKNOWN_TYPE;

    daName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".Oper.ctlVal");
    da = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, daName);

    if (da) {
        ctlValType = da->type;
    }

    daName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".stVal");
    da = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, daName);

    if (da) {
        stValType = da->type;
    }

    daName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".mxVal.f");
    da = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, daName);

    if (da) {
        mxValType = da->type;
    }

    daName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".mxVal.i");
    da = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, daName);

    if (da) {
        mxValType = da->type;
    }

    daName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".valWTr");
    da = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, daName);

    if (da) {
        mxValType = da->type;
    }

    if (stValType == IEC61850_BOOLEAN && ctlValType == IEC61850_BOOLEAN)
    {
        self->cdc = CST_SPCTRK; /* SPC */
    }
    else if (stValType == IEC61850_CODEDENUM && ctlValType == IEC61850_BOOLEAN)
    {
        self->cdc = CST_DPCTRK; /* DPC */
    }
    else if (stValType == IEC61850_INT32 && ctlValType == IEC61850_INT32)
    {
        self->cdc = CST_INCTRK; /* INC */
    }
    else if (stValType == IEC61850_ENUMERATED && ctlValType == IEC61850_ENUMERATED)
    {
        self->cdc = CST_ENCTRK1; /* ENC */
    }
    else if (stValType == IEC61850_CONSTRUCTED && ctlValType == IEC61850_CODEDENUM)
    {
        self->cdc = CST_BSCTRK; /* BSC */
    }
    else if (stValType == IEC61850_CONSTRUCTED && ctlValType == IEC61850_INT8)
    {
        self->cdc = CST_ISCTRK; /* ISC */
    }
    else if ((mxValType == IEC61850_FLOAT32 || mxValType == IEC61850_FLOAT64) && (ctlValType == IEC61850_CONSTRUCTED))
    {
        self->cdc = CST_APCFTRK; /* APC (float) */
    }
    else if ((mxValType == IEC61850_INT32) && (ctlValType == IEC61850_CONSTRUCTED))
    {
        self->cdc = CST_APCINTTRK; /* APC (int) */
    }
    else if ((stValType == IEC61850_FLOAT32 || stValType == IEC61850_FLOAT64) && ctlValType == IEC61850_CODEDENUM)
    {
        self->cdc = CST_BACTRK; /* BAC */
    }
    else
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ERROR - stValType (%i) or ctlValType (%i) could not be determined!\n", stValType, ctlValType);
        self->cdc = CST_NONE;
    }
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

    char* stSeldName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".stSeld");

    self->stSeld = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, stSeldName);

    if ((self->stSeld) && (self->stSeld->type != IEC61850_BOOLEAN))
    {
        self->stSeld = NULL;

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ERROR - stSeld of wrong type!\n");
    }

    char* opRcvdName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".opRcvd");

    self->opRcvd = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, opRcvdName);

    if ((self->opRcvd) && (self->opRcvd->type != IEC61850_BOOLEAN))
    {
        self->opRcvd = NULL;

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ERROR - opRcvd of wrong type!\n");
    }

    char* opOkName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".opOk");

    self->opOk = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, opOkName);

    if ((self->opOk) && (self->opOk->type != IEC61850_BOOLEAN))
    {
        self->opOk = NULL;

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ERROR - opOk of wrong type!\n");
    }

    char* tOpOkName = StringUtils_createStringInBuffer(strBuf, 130, 6, self->mmsDomain->domainName, "/", self->lnName, ".", self->name, ".tOpOk");

    self->tOpOk = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, tOpOkName);

    if ((self->tOpOk) && (self->tOpOk->type != IEC61850_TIMESTAMP))
    {
        self->tOpOk = NULL;

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ERROR - tOpOk of wrong type!\n");
    }

    self->error = MmsValue_newIntegerFromInt32(0);
    self->addCause = MmsValue_newIntegerFromInt32(0);

    if (ctlModel != NULL)
    {
        int ctlModelVal = MmsValue_toInt32(ctlModel);

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER:  ctlModel: %i\n", ctlModelVal);

        if ((ctlModelVal < 0) || (ctlModelVal > 4)) {
            ctlModelVal = 1;
            if (DEBUG_IED_SERVER)
                printf("IED_SERVER:  invalid control model!\n");
        }

        self->ctlModel = ctlModelVal;

        if ((ctlModelVal == 2) || (ctlModelVal == 4)) /* SBO */
            setState(self, STATE_UNSELECTED);
        else
            setState(self, STATE_READY);
    }
}

static void
ControlObject_handlePendingEvents(ControlObject* self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_wait(self->pendingEventsLock);
#endif

    if (self->pendingEvents > 0)
    {
        if (self->pendingEvents & PENDING_EVENT_SELECTED)
        {
            if (self->stSeld)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->stSeld, true);

            self->pendingEvents &= ~(PENDING_EVENT_SELECTED);
        }

        if (self->pendingEvents & PENDING_EVENT_UNSELECTED)
        {
            if (self->stSeld)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->stSeld, false);

            self->pendingEvents &= ~(PENDING_EVENT_UNSELECTED);
        }

        if (self->pendingEvents & PENDING_EVENT_OP_RCVD_TRUE)
        {
            if (self->opRcvd)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->opRcvd, true);

            self->pendingEvents &= ~(PENDING_EVENT_OP_RCVD_TRUE);
        }

        if (self->pendingEvents & PENDING_EVENT_OP_RCVD_FALSE)
        {
            if (self->opRcvd)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->opRcvd, false);

            self->pendingEvents &= ~(PENDING_EVENT_OP_RCVD_FALSE);
        }

        if (self->pendingEvents & PENDING_EVENT_OP_OK_TRUE)
        {
            if (self->opOk)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->opOk, true);

            self->pendingEvents &= ~(PENDING_EVENT_OP_OK_TRUE);
        }

        if (self->pendingEvents & PENDING_EVENT_OP_OK_FALSE)
        {
            if (self->opOk)
                IedServer_updateBooleanAttributeValue(self->iedServer, self->opOk, false);

            self->pendingEvents &= ~(PENDING_EVENT_OP_OK_FALSE);
        }
    }

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

void
ControlObject_destroy(ControlObject* self)
{
    if (self)
    {
        if (self->mmsValue)
            MmsValue_delete(self->mmsValue);

        if (self->error)
            MmsValue_delete(self->error);

        if (self->addCause)
            MmsValue_delete(self->addCause);

        if (self->ctlVal)
            MmsValue_delete(self->ctlVal);

        if (self->ctlNum)
            MmsValue_delete(self->ctlNum);

        if (self->origin)
            MmsValue_delete(self->origin);

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

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

        if (self->pendingEventsLock)
            Semaphore_destroy(self->pendingEventsLock);
#endif

        GLOBAL_FREEMEM(self);
    }
}

char*
ControlObject_getName(ControlObject* self)
{
    return self->name;
}

char*
ControlObject_getLNName(ControlObject* self)
{
    return self->lnName;
}

MmsDomain*
ControlObject_getDomain(ControlObject* self)
{
    return self->mmsDomain;
}

void
ControlObject_setMmsValue(ControlObject* self, MmsValue* value)
{
    self->mmsValue = value;
}

void
ControlObject_setTypeSpec(ControlObject* self, MmsVariableSpecification* typeSpec)
{
    self->typeSpec = typeSpec;
}

MmsVariableSpecification*
ControlObject_getTypeSpec(ControlObject* self)
{
    return self->typeSpec;
}

MmsValue*
ControlObject_getMmsValue(ControlObject* self)
{
    return self->mmsValue;
}

bool
ControlObject_unselect(ControlObject* self, MmsServerConnection connection, MmsMapping* mmsMapping)
{
    if (self->mmsConnection == connection)
    {
        abortControlOperation(self, true, SELECT_STATE_REASON_DISCONNECTED, mmsMapping);
        return true;
    }
    else
        return false;
}

void
ControlObject_installListener(ControlObject* self, ControlHandler listener, void* parameter)
{
    self->operateHandler = listener;
    self->operateHandlerParameter = parameter;
}

void
ControlObject_installCheckHandler(ControlObject* self, ControlPerformCheckHandler handler, void* parameter)
{
    self->checkHandler = handler;
    self->checkHandlerParameter = parameter;
}

void
ControlObject_installWaitForExecutionHandler(ControlObject* self, ControlWaitForExecutionHandler handler,
        void* parameter)
{
    self->waitForExecutionHandler = handler;
    self->waitForExecutionHandlerParameter = parameter;
}

void
ControlObject_installSelectStateChangedHandler(ControlObject* self, ControlSelectStateChangedHandler handler,
        void* parameter)
{
    self->selectStateChangedHandler = handler;
    self->selectStateChangedHandlerParameter = parameter;
}

void
ControlObject_updateControlModel(ControlObject* self, ControlModel value, DataObject* ctlObject)
{
    self->ctlModel = (uint32_t) value;

    if ((self->ctlModel == 2) || (self->ctlModel == 4)) /* SBO */
        setState(self, STATE_UNSELECTED);
    else
        setState(self, STATE_READY);

    DataAttribute* ctlModel = (DataAttribute*) ModelNode_getChild((ModelNode*) ctlObject, "ctlModel");

    if (ctlModel) {
        IedServer_updateInt32AttributeValue(self->iedServer, ctlModel, (int32_t) value);
    }
}

void
Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs)
{
    if (currentTimeInMs >= self->nextControlTimeout)
    {
        /* invalidate nextControlTimeout */
        self->nextControlTimeout = (uint64_t) 0xFFFFFFFFFFFFFFFFLLU;

        LinkedList element = LinkedList_getNext(self->controlObjects);

        while (element != NULL)
        {
            ControlObject* controlObject = (ControlObject*) element->data;

            if (controlObject->state != STATE_UNSELECTED)
            {
                if ((controlObject->ctlModel == 1) || (controlObject->ctlModel == 3))
                {
                    if (controlObject->state == STATE_READY)
                    {
                        element = LinkedList_getNext(element);
                        continue;
                    }
                }

                if (controlObject->state == STATE_WAIT_FOR_ACTIVATION_TIME)
                {
                    if (controlObject->operateTime <= currentTimeInMs)
                    {
                        /* enter state Perform Test */
                        setOpRcvd(controlObject, true);

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: time activated operate: perform test\n");

                        controlObject->timeActivatedOperate = false;

                        CheckHandlerResult checkResult = CONTROL_ACCEPTED;

                        if (controlObject->checkHandler != NULL)
                        {
                            /* perform operative tests */

                            controlObject->errorValue = CONTROL_ERROR_NO_ERROR;
                            controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_INTERLOCKING;

                            checkResult = controlObject->checkHandler((ControlAction) controlObject,
                                    controlObject->checkHandlerParameter, controlObject->ctlVal, controlObject->testMode,
                                    controlObject->interlockCheck);
                        }

                        if (checkResult == CONTROL_ACCEPTED)
                        {
                            if (DEBUG_IED_SERVER)
                                printf("IED_SERVER: time activated operate: command accepted\n");

                            /* leave state Perform Test */
                            setOpRcvd(controlObject, false);

                            executeControlTask(self, controlObject, currentTimeInMs);
                        }
                        else
                        {
                            ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "Oper",
                                    controlObject->errorValue, controlObject->addCauseValue,
                                        controlObject->ctlNum, controlObject->origin, false);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                            updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_TIME_ACTIVATED_OPERATE,
                                    convertCheckHandlerResultToServiceError(checkResult));
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

                            /* leave state Perform Test */
                            setOpRcvd(controlObject, false);

                            abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self);

                            resetAddCause(controlObject);
                        }
                    }
                    else {
                        updateNextControlTimeout(self, controlObject->operateTime);
                    }

                } /* if (controlObject->state == STATE_WAIT_FOR_ACTICATION_TIME) */
                else if (!((controlObject->state == STATE_UNSELECTED) || (controlObject->state == STATE_READY))) {
                    executeControlTask(self, controlObject, currentTimeInMs);
                }
                else if (controlObject->state == STATE_READY) {
                    checkSelectTimeout(controlObject, currentTimeInMs, self);
                }
                }

            ControlObject_handlePendingEvents(controlObject);

            element = LinkedList_getNext(element);
        }
    }
}

ControlObject*
Control_lookupControlObject(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
{
    LinkedList element = LinkedList_getNext(self->controlObjects);

    while (element != NULL)
    {
        ControlObject* controlObject = (ControlObject*) element->data;

        if (ControlObject_getDomain(controlObject) == domain)
        {
            if (strcmp(ControlObject_getLNName(controlObject), lnName) == 0)
            {
                if (strcmp(ControlObject_getName(controlObject), objectName) == 0)
                {
                    return controlObject;
                }
            }
        }

        element = LinkedList_getNext(element);
    }

    return NULL;
}

static MmsValue*
getCtlVal(MmsValue* operParameters)
{
    if (MmsValue_getType(operParameters) == MMS_STRUCTURE)
    {
        if (MmsValue_getArraySize(operParameters) > 5)
        {
            return MmsValue_getElement(operParameters, 0);
        }
    }

    return NULL;
}

void
ControlObject_sendCommandTerminationPositive(ControlObject* self)
{
    char itemId[68]; /* 64 characters + space for FC + separator + string terminator */

    StringUtils_createStringInBuffer(itemId, 68, 4, self->lnName, "$CO$", self->name, "$Oper");

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: send CommandTermination+: %s\n", itemId);

    MmsVariableAccessSpecification varSpec;

    varSpec.itemId = itemId;
    varSpec.domainId = MmsDomain_getName(self->mmsDomain);

    struct sLinkedList _varSpecList1;
    _varSpecList1.next = NULL;
    struct sLinkedList _varSpecList;
    _varSpecList.data = NULL;
    _varSpecList.next = &_varSpecList1;

    struct sLinkedList _values1;
    _values1.next = NULL;
    struct sLinkedList _values;
    _values.data = NULL;
    _values.next = &_values1;

    _varSpecList1.data = &varSpec;
    _values1.data = self->oper;

    MmsServerConnection_sendInformationReportListOfVariables(self->mmsConnection, &_varSpecList, &_values, false);
}

void
ControlObject_sendCommandTerminationNegative(ControlObject* self)
{
    /* create LastApplError */

    struct sLinkedList _varSpecList2;
    _varSpecList2.next = NULL;
    struct sLinkedList _varSpecList1;
    _varSpecList1.next = &_varSpecList2;
    struct sLinkedList _varSpecList;
    _varSpecList.data = NULL;
    _varSpecList.next = &_varSpecList1;

    struct sLinkedList _values2;
    _values2.next = NULL;
    struct sLinkedList _values1;
    _values1.next = &_values2;
    struct sLinkedList _values;
    _values.data = NULL;
    _values.next = &_values1;

    MmsValue lastApplErrorMemory;

    MmsValue* lastApplError = &lastApplErrorMemory;
    lastApplError->type = MMS_STRUCTURE;
    lastApplError->value.structure.size = 5;

    MmsValue* componentContainer[5];

    lastApplError->value.structure.components = componentContainer;

    char ctlObj[130];

    StringUtils_createStringInBuffer(ctlObj, 130, 6, MmsDomain_getName(self->mmsDomain), "/",
            self->lnName, "$CO$", self->name, "$Oper");

    MmsValue ctlObjValueMemory;

    MmsValue* ctlObjValue = &ctlObjValueMemory;
    ctlObjValue->type = MMS_VISIBLE_STRING;
    ctlObjValue->value.visibleString.buf = ctlObj;
    ctlObjValue->value.visibleString.size = sizeof(ctlObj);

    MmsValue_setElement(lastApplError, 0, ctlObjValue);

    MmsValue_setInt32(self->error, self->errorValue);
    MmsValue_setInt32(self->addCause, self->addCauseValue);

    MmsValue_setElement(lastApplError, 1, self->error);
    MmsValue_setElement(lastApplError, 2, self->origin);
    MmsValue_setElement(lastApplError, 3, self->ctlNum);
    MmsValue_setElement(lastApplError, 4, self->addCause);

    MmsVariableAccessSpecification lastApplErrorVarSpec;

    lastApplErrorVarSpec.itemId = "LastApplError";
    lastApplErrorVarSpec.domainId = NULL;

    /* create oper variable */

    char itemId[130];

    StringUtils_createStringInBuffer(itemId, 130, 4, self->lnName, "$CO$", self->name, "$Oper");

    char* domainId = MmsDomain_getName(self->mmsDomain);

    MmsVariableAccessSpecification operVarSpec;

    operVarSpec.itemId = itemId;
    operVarSpec.domainId = domainId;

    /* create response */

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: send CommandTermination-: %s\n", itemId);

    _varSpecList1.data = &lastApplErrorVarSpec;
    _varSpecList2.data = &operVarSpec;

    _values1.data = lastApplError;
    _values2.data = self->oper;

    MmsServerConnection_sendInformationReportListOfVariables(self->mmsConnection, &_varSpecList, &_values, false);
} /* ControlObject_sendCommandTerminationNegative() */

void
ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connection, char* ctlVariable, int error,
        ControlAddCause addCause, MmsValue* ctlNum, MmsValue* origin, bool handlerMode)
{
    MmsValue lastApplErrorMemory;

    MmsValue* lastApplError = &lastApplErrorMemory;
    lastApplError->type = MMS_STRUCTURE;
    lastApplError->value.structure.size = 5;

    MmsValue* componentContainer[5];

    lastApplError->value.structure.components = componentContainer;

    char ctlObj[130];

    StringUtils_createStringInBuffer(ctlObj, 130, 7, MmsDomain_getName(self->mmsDomain), "/",
            self->lnName, "$CO$", self->name, "$", ctlVariable);

    if (DEBUG_IED_SERVER)
    {
        printf("IED_SERVER: sendLastApplError:\n");
        printf("IED_SERVER:    control object: %s\n", ctlObj);
        printf("IED_SERVER:    ctlNum: %u\n", MmsValue_toUint32(ctlNum));
    }

    MmsValue ctlObjValueMemory;

    MmsValue* ctlObjValue = &ctlObjValueMemory;
    ctlObjValue->type = MMS_VISIBLE_STRING;
    ctlObjValue->value.visibleString.buf = ctlObj;
    ctlObjValue->value.visibleString.size = sizeof(ctlObj);

    MmsValue_setElement(lastApplError, 0, ctlObjValue);

    MmsValue_setInt32(self->error, error);
    MmsValue_setInt32(self->addCause, addCause);

    MmsValue_setElement(lastApplError, 1, self->error);
    MmsValue_setElement(lastApplError, 2, origin);
    MmsValue_setElement(lastApplError, 3, ctlNum);
    MmsValue_setElement(lastApplError, 4, self->addCause);

    MmsServerConnection_sendInformationReportSingleVariableVMDSpecific(connection,
            "LastApplError", lastApplError, handlerMode);

    self->addCauseValue = addCause;
}

static void
updateControlParameters(ControlObject* controlObject, MmsValue* ctlVal, MmsValue* ctlNum, MmsValue* origin, bool synchroCheck, bool interlockCheck)
{
    MmsValue_update(controlObject->ctlVal, ctlVal);
    MmsValue_update(controlObject->ctlNum, ctlNum);
    MmsValue_update(controlObject->origin, origin);
    controlObject->synchroCheck = synchroCheck;
    controlObject->interlockCheck = interlockCheck;

    if (controlObject->ctlNumSt)
        MmsValue_update(controlObject->ctlNumSt, ctlNum);

    if (controlObject->originSt)
        MmsValue_update(controlObject->originSt, origin);
}

static bool
doesElementEquals(char* element, char* name)
{
    int i = 0;

    while (name[i] != 0)
    {
        if (element[i] == 0)
            return false;

        if (element[i] != name[i])
            return false;

        i++;
    }

    if ((element[i] != 0) && (element[i] != '$'))
        return false;

    return true;
}

MmsValue*
Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
        MmsServerConnection connection, bool isDirectAccess)
{
    MmsValue* value = NULL;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: readAccessControlObject: %s\n", variableIdOrig);

    char variableId[129];

    StringUtils_copyStringMax(variableId, 129, variableIdOrig);

    char* separator = strchr(variableId, '$');

    if (separator == NULL)
        return NULL;

    *separator = 0;

    char* lnName = variableId;

    char* objectName = MmsMapping_getNextNameElement(separator + 1);

    if (objectName == NULL)
        return NULL;

    char* varName =  MmsMapping_getNextNameElement(objectName);

    if (varName != NULL)
    {
        bool foundVar = false;

        char* nextVarName = varName;

        do
        {
            if (doesElementEquals(varName, "Oper") ||
                doesElementEquals(varName, "SBO") ||
                doesElementEquals(varName, "SBOw") ||
                doesElementEquals(varName, "Cancel"))
            {
                *(varName - 1) = 0;
                foundVar = true;
                break;
            }

            nextVarName  = MmsMapping_getNextNameElement(varName);

            if (nextVarName)
                varName = nextVarName;

        } while (nextVarName);

        if (foundVar == false)
            varName = NULL;
    }

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: read_access control object: objectName: (%s) varName: (%s)\n", objectName, varName);

    ControlObject* controlObject = Control_lookupControlObject(self, domain, lnName, objectName);

    if (controlObject != NULL)
    {
        if (varName != NULL)
        {
            if (strcmp(varName, "Oper") == 0)
                value = controlObject->oper;
            else if (strcmp(varName, "SBOw") == 0)
                value = controlObject->sbow;
            else if (strcmp(varName, "SBO") == 0)
            {
                if (controlObject->ctlModel == 2)
                {
                    uint64_t currentTime = Hal_getTimeInMs();

                    value = &emptyString;

                    if (isDirectAccess == true)
                    {
                        checkSelectTimeout(controlObject, currentTime, self);

                        if (getState(controlObject) == STATE_UNSELECTED)
                        {
                            CheckHandlerResult checkResult = CONTROL_ACCEPTED;

                            /* opRcvd must not be set here! */

                            controlObject->addCauseValue = ADD_CAUSE_UNKNOWN;
                            controlObject->mmsConnection = connection;

                            if (controlObject->checkHandler != NULL)
                            {
                                /* perform operative tests */

                                controlObject->isSelect = 1;

                                checkResult = controlObject->checkHandler((ControlAction) controlObject,
                                        controlObject->checkHandlerParameter, NULL, false, false);

                                controlObject->isSelect = 0;
                            }

                            if (checkResult == CONTROL_ACCEPTED)
                            {
                                selectObject(controlObject, currentTime, connection, self);
                                value = controlObject->sbo;

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT, IEC61850_SERVICE_ERROR_NO_ERROR);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
                            }
                            else if (checkResult == CONTROL_WAITING_FOR_SELECT)
                            {
                                controlObject->mmsConnection = connection;
                                controlObject->operateInvokeId = MmsServerConnection_getLastInvokeId(connection);
                                setState(controlObject, STATE_WAIT_FOR_SELECT);
                                value = &delayedResponse;
                            }
                            else
                            {
#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                                updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT,
                                        convertCheckHandlerResultToServiceError(checkResult));
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
                            }
                        }
                    }
                }
                else
                {
                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: select not applicable for control model %u\n", controlObject->ctlModel);

                    value = &emptyString;
                }
            }

            else if (strcmp(varName, "Cancel") == 0)
                value = controlObject->cancel;
            else
            {
                value = MmsValue_getSubElement(ControlObject_getMmsValue(controlObject),
                        ControlObject_getTypeSpec(controlObject), varName);
            }
        }
        else {
            value = ControlObject_getMmsValue(controlObject);
        }
    }
    else
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: Control object not found %s/%s.%s\n", domain->domainName, lnName, objectName);
    }

    return value;
}

static bool
checkValidityOfOriginParameter(MmsValue* origin)
{
    if (MmsValue_getType(origin) != MMS_STRUCTURE)
        return false;

    if (MmsValue_getArraySize(origin) != 2)
        return false;

    MmsValue* orIdent = MmsValue_getElement(origin, 1);

    if (MmsValue_getType(orIdent) != MMS_OCTET_STRING)
        return false;

    if (MmsValue_getOctetStringSize(orIdent) > 64)
        return false;

    MmsValue* orCat = MmsValue_getElement(origin, 0);

    if (MmsValue_getType(orCat) != MMS_INTEGER)
        return false;

    int orCatIntValue = MmsValue_toInt32(orCat);

    if ((orCatIntValue < 0) || (orCatIntValue > 8))
        return false;

    return true;
}

MmsDataAccessError
Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
        MmsValue* value, MmsServerConnection connection)
{
    MmsDataAccessError indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
    IEC61850_ServiceError serviceError = IEC61850_SERVICE_ERROR_NO_ERROR;
    ControlObject* controlObject = NULL;

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: writeAccessControlObject: %s\n", variableIdOrig);

    IEC61850_ServiceType serviceType = IEC61850_SERVICE_TYPE_UNKOWN;

    char variableId[65];

    StringUtils_copyStringMax(variableId, 65, variableIdOrig);

    char* separator = strchr(variableId, '$');

    if (separator == NULL)
        goto free_and_return;

    *separator = 0;

    char* lnName = variableId;

    if (lnName == NULL)
        goto free_and_return;

    char* objectName = MmsMapping_getNextNameElement(separator + 1);

    if (objectName == NULL)
        goto free_and_return;

    char* varName =  MmsMapping_getNextNameElement(objectName);

    if (varName != NULL)
    {
        bool foundVar = false;

        char* nextVarName = varName;

        do
        {
            if (doesElementEquals(varName, "Oper") ||
                doesElementEquals(varName, "SBO") ||
                doesElementEquals(varName, "SBOw") ||
                doesElementEquals(varName, "Cancel"))
            {
                *(varName - 1) = 0;
                foundVar = true;
                break;
            }

            nextVarName  = MmsMapping_getNextNameElement(varName);

            if (nextVarName != NULL)
                varName = nextVarName;

        } while (nextVarName != NULL);

        if (foundVar == false)
            varName = NULL;
    }

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: write access control: objectName: (%s) varName: (%s)\n", objectName, varName);

    if (varName == NULL)
    {
        indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
        goto free_and_return;
    }

    controlObject = Control_lookupControlObject(self, domain, lnName, objectName);

    if (controlObject == NULL)
    {
        indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
        goto free_and_return;
    }

    if (controlObject->ctlModel == CONTROL_MODEL_STATUS_ONLY)
    {
        indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
        goto free_and_return;
    }

    if (strcmp(varName, "SBOw") == 0) /* select with value */
    {
        serviceType = IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES;

        if (controlObject->ctlModel == 4)
        {
            if (controlObject->sbow)
            {
                if (MmsValue_update(controlObject->sbow, value) == false)
                {
                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: SBOw - type mismatch\n");
                }
            }

            MmsValue* ctlVal = getCtlVal(value);

            if (ctlVal != NULL)
            {
                MmsValue* ctlNum = getOperParameterCtlNum(value);
                MmsValue* origin = getOperParameterOrigin(value);
                MmsValue* check = getOperParameterCheck(value);
                MmsValue* test = getOperParameterTest(value);
                MmsValue* t = getOperParameterTime(value);

                if (checkValidityOfOriginParameter(origin) == false)
                {
                    indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;

                    ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR,
                            ADD_CAUSE_SELECT_FAILED, ctlNum, origin, true);

                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: SBOw - invalid origin value\n");

                    goto free_and_return;
                }

                if (t)
                {
                    Timestamp_fromMmsValue(&(controlObject->T), t);
                }

                int state = getState(controlObject);

                uint64_t currentTime = Hal_getTimeInMs();

                checkSelectTimeout(controlObject, currentTime, self);

                if (state != STATE_UNSELECTED)
                {
                    if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION))
                    {
                        indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

                        ControlObject_sendLastApplError(controlObject, connection, "SBOw",
                                CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION,
                                ctlNum, origin, true);

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: SBOw - select failed - already in execution!\n");

                        goto free_and_return;
                    }
                    else
                    {
                        indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

                        ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR,
                                ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true);

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: SBOw - select failed - already selected!\n");

                        goto free_and_return;
                    }
                }
                else
                {
                    CheckHandlerResult checkResult = CONTROL_ACCEPTED;

                    /* opRcvd must not be set here! */

                    bool interlockCheck = MmsValue_getBitStringBit(check, 1);
                    bool synchroCheck = MmsValue_getBitStringBit(check, 0);
                    bool testCondition = MmsValue_getBoolean(test);

                    controlObject->addCauseValue = ADD_CAUSE_SELECT_FAILED;

                    updateControlParameters(controlObject, ctlVal, ctlNum, origin, synchroCheck, interlockCheck);

                    controlObject->testMode = testCondition;

                    if (controlObject->checkHandler != NULL)
                    {
                        /* perform operative tests */

                        controlObject->isSelect = 1;

                        controlObject->mmsConnection = connection;

                        checkResult = controlObject->checkHandler((ControlAction) controlObject,
                                controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck);

                        controlObject->isSelect = 0;
                    }

                    if (checkResult == CONTROL_ACCEPTED)
                    {
                        selectObject(controlObject, currentTime, connection, self);

                        indication = DATA_ACCESS_ERROR_SUCCESS;

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: SBOw - selected successful\n");
                    }
                    else if (checkResult == CONTROL_WAITING_FOR_SELECT)
                    {
                        controlObject->mmsConnection = connection;
                        controlObject->operateInvokeId = MmsServerConnection_getLastInvokeId(connection);

                        MmsValue_update(controlObject->ctlVal, ctlVal);
                        MmsValue_update(controlObject->ctlNum, ctlNum);
                        MmsValue_update(controlObject->origin, origin);

                        setState(controlObject, STATE_WAIT_FOR_SELECT);

                        updateNextControlTimeout(self, Hal_getTimeInMs() + 100);

                        indication = DATA_ACCESS_ERROR_NO_RESPONSE;
                    }
                    else
                    {
                        indication = (MmsDataAccessError) checkResult;

                        ControlObject_sendLastApplError(controlObject, connection, "SBOw", controlObject->errorValue,
                                controlObject->addCauseValue, ctlNum, origin, true);

                        controlObject->mmsConnection = NULL;

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: SBOw - select rejected by application!\n");
                    }
                }
            }
            else
            {
                indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            }
        }
        else
        {
            indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto free_and_return;
        }
    }
    else if (strcmp(varName, "Oper") == 0)
    {
        serviceType = IEC61850_SERVICE_TYPE_OPERATE;

        MmsValue* ctlVal = getCtlVal(value);
        MmsValue* test = getOperParameterTest(value);
        MmsValue* ctlNum = getOperParameterCtlNum(value);
        MmsValue* origin = getOperParameterOrigin(value);
        MmsValue* check = getOperParameterCheck(value);
        MmsValue* timeParameter = getOperParameterTime(value);

        if ((ctlVal == NULL) || (test == NULL) || (ctlNum == NULL) || (origin == NULL) || (check == NULL)
                || (timeParameter == NULL))
        {
            indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            goto free_and_return;
        }

        Timestamp_fromMmsValue(&(controlObject->T), timeParameter);

        if (checkValidityOfOriginParameter(origin) == false)
        {
            indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;

            ControlObject_sendLastApplError(controlObject, connection, "Oper",
                    CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS,
                        ctlNum, origin, true);

            if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4))
            {
                unselectObject(controlObject, SELECT_STATE_REASON_OPERATE_FAILED, self);
            }

            goto free_and_return;
        }

        uint64_t currentTime = Hal_getTimeInMs();

        checkSelectTimeout(controlObject, currentTime, self);

        int state = getState(controlObject);

        if (state == STATE_WAIT_FOR_ACTIVATION_TIME)
        {
            indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

            ControlObject_sendLastApplError(controlObject, connection, "Oper",
                    CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION,
                        ctlNum, origin, true);

            goto free_and_return;
        }
        else if (state == STATE_READY)
        {
            bool interlockCheck = MmsValue_getBitStringBit(check, 1);
            bool synchroCheck = MmsValue_getBitStringBit(check, 0);

            bool testCondition = MmsValue_getBoolean(test);

            if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4))
            {
                if (controlObject->mmsConnection != connection)
                {
                    indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
                    if (DEBUG_IED_SERVER)
                        printf("IED_SERVER: Oper - operate from wrong client connection!\n");

                    ControlObject_sendLastApplError(controlObject, connection, "Oper", CONTROL_ERROR_NO_ERROR,
                            ADD_CAUSE_LOCKED_BY_OTHER_CLIENT, ctlNum, origin, true);

                    goto free_and_return;
                }

                if (controlObject->ctlModel == 4) /* select-before-operate with enhanced security */
                {
                    if ((MmsValue_equals(ctlVal, controlObject->ctlVal) &&
                         MmsValue_equals(origin, controlObject->origin) &&
                         MmsValue_equals(ctlNum, controlObject->ctlNum) &&
                         (controlObject->interlockCheck == interlockCheck) &&
                         (controlObject->synchroCheck == synchroCheck) &&
                         (controlObject->testMode == testCondition)
                         ) == false)
                    {
                        indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;

                        ControlObject_sendLastApplError(controlObject, connection, "Oper",
                                CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS,
                                    ctlNum, origin, true);

                        unselectObject(controlObject, SELECT_STATE_REASON_OPERATE_FAILED, self);

                        goto free_and_return;
                    }
                }
            }

            controlObject->testMode = testCondition;

            updateControlParameters(controlObject, ctlVal, ctlNum, origin, synchroCheck, interlockCheck);

            MmsValue* operTm = getOperParameterOperTime(value);

            if (operTm != NULL)
            {
                controlObject->operateTime = MmsValue_getUtcTimeInMs(operTm);

                if (controlObject->operateTime > currentTime)
                {
                    controlObject->timeActivatedOperate = true;
                    controlObject->synchroCheck = synchroCheck;
                    controlObject->interlockCheck = interlockCheck;
                    controlObject->mmsConnection = connection;

                    CheckHandlerResult checkResult = CONTROL_ACCEPTED;
                    if (controlObject->checkHandler != NULL) 
                    {
                        /* perform operative tests */

                        checkResult = controlObject->checkHandler((ControlAction) controlObject,
                            controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck);
                    }

                    if (checkResult == CONTROL_ACCEPTED)
                    {
                        initiateControlTask(controlObject);

                        setState(controlObject, STATE_WAIT_FOR_ACTIVATION_TIME);

                        updateNextControlTimeout(self, controlObject->operateTime);

                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: Oper - activate time activated control\n");

                        indication = DATA_ACCESS_ERROR_SUCCESS;
                    }
                    else
                    {
                        indication = (MmsDataAccessError) checkResult;
                    }
                }
            }
            else
            {
                controlObject->operateTime = 0;
            }

            MmsValue_update(controlObject->oper, value);

            if (controlObject->timeActivatedOperate == false)
            {
                CheckHandlerResult checkResult = CONTROL_ACCEPTED;

                /* enter state Perform Test */
                setOpRcvd(controlObject, true);

                controlObject->errorValue = CONTROL_ERROR_NO_ERROR;
                controlObject->addCauseValue = ADD_CAUSE_UNKNOWN;
                controlObject->mmsConnection = connection;

                if (controlObject->checkHandler != NULL)
                {
                    /* perform operative tests */

                    checkResult = controlObject->checkHandler((ControlAction) controlObject,
                            controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck);
                }

                if (checkResult == CONTROL_ACCEPTED)
                {
                    indication = DATA_ACCESS_ERROR_NO_RESPONSE;

                    controlObject->mmsConnection = connection;

                    controlObject->operateInvokeId = MmsServerConnection_getLastInvokeId(connection);

                    setState(controlObject, STATE_WAIT_FOR_EXECUTION);

                    /* leave state Perform Test */
                    setOpRcvd(controlObject, false);

                    initiateControlTask(controlObject);

                    updateNextControlTimeout(self, currentTime);
                }
                else
                {
                    indication = (MmsDataAccessError) checkResult;

                    /* leave state Perform Test */
                    setOpRcvd(controlObject, false);

                    abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self);

                    ControlObject_sendLastApplError(controlObject, connection, "Oper",
                            controlObject->errorValue, controlObject->addCauseValue,
                                ctlNum, origin, true);
                }
            }

        }
        else if (state == STATE_UNSELECTED)
        {
            if (DEBUG_IED_SERVER)
                printf("IED_SERVER: Oper failed - control not selected!\n");

            indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            ControlObject_sendLastApplError(controlObject, connection, "Oper",
                    CONTROL_ERROR_NO_ERROR, ADD_CAUSE_OBJECT_NOT_SELECTED,
                        ctlNum, origin, true);

            serviceError = IEC61850_SERVICE_ERROR_CONTROL_MUST_BE_SELECTED;

            goto free_and_return;
        }
        else if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION))
        {
            if (DEBUG_IED_SERVER)
                printf("IED_SERVER: Oper failed - control already being executed!\n");

            indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

            ControlObject_sendLastApplError(controlObject, connection, "Oper",
                    CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION,
                    ctlNum, origin, true);

            goto free_and_return;
        }
    }
    else if (strcmp(varName, "Cancel") == 0)
    {
        serviceType = IEC61850_SERVICE_TYPE_CANCEL;

        int state = getState(controlObject);

        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: control received cancel (state: %i)!\n", state);

        MmsValue* ctlNum = getCancelParameterCtlNum(value);
        MmsValue* origin = getCancelParameterOrigin(value);

        if ((ctlNum == NULL) || (origin == NULL))
        {
            indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
            if (DEBUG_IED_SERVER)
                printf("IED_SERVER: Invalid cancel message!\n");
            goto free_and_return;
        }

        if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION))
        {
            indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;

            ControlObject_sendLastApplError(controlObject, connection, "Cancel",
                    CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION,
                    ctlNum, origin, true);

            goto free_and_return;
        }

        if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4))
        {
            if (state != STATE_UNSELECTED)
            {
                if (controlObject->mmsConnection == connection)
                {
                    indication = DATA_ACCESS_ERROR_SUCCESS;
                    unselectObject(controlObject, SELECT_STATE_REASON_CANCELED, self);
                    goto free_and_return;
                }
                else
                {
                    indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
                    ControlObject_sendLastApplError(controlObject, connection, "Cancel",
                            CONTROL_ERROR_NO_ERROR, ADD_CAUSE_LOCKED_BY_OTHER_CLIENT,
                                ctlNum, origin, true);
                }
            }
            else
            {
                indication = DATA_ACCESS_ERROR_SUCCESS;
            }
        }

        if (controlObject->timeActivatedOperate)
        {
            controlObject->timeActivatedOperate = false;
            abortControlOperation(controlObject, false, SELECT_STATE_REASON_CANCELED, self);

            indication = DATA_ACCESS_ERROR_SUCCESS;

            goto free_and_return;
        }
    }

free_and_return:

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
    if (controlObject)
    {
        if (serviceError == IEC61850_SERVICE_ERROR_NO_ERROR)
        {
            if (indication != DATA_ACCESS_ERROR_NO_RESPONSE)
            {
                updateGenericTrackingObjectValues(self, controlObject, serviceType,
                        private_IedServer_convertMmsDataAccessErrorToServiceError(indication));
            }
        }
        else
        {
            updateGenericTrackingObjectValues(self, controlObject, serviceType, serviceError);
        }
    }
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

    resetAddCause(controlObject);

    return indication;
}

void
ControlAction_setError(ControlAction self, ControlLastApplError error)
{
    ControlObject* controlObject = (ControlObject*) self;

    controlObject->errorValue = error;
}

void
ControlAction_setAddCause(ControlAction self, ControlAddCause addCause)
{
    ControlObject* controlObject = (ControlObject*) self;

    controlObject->addCauseValue = addCause;
}

int
ControlAction_getOrCat(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    if (controlObject->origin)
    {
        MmsValue* orCat = MmsValue_getElement(controlObject->origin, 0);

        if (orCat) {
            return MmsValue_toInt32(orCat);
        }
    }

    return 0;
}

uint8_t*
ControlAction_getOrIdent(ControlAction self, int* orIdentSize)
{
    ControlObject* controlObject = (ControlObject*) self;

    if (controlObject->origin)
    {
        MmsValue* orIdent = MmsValue_getElement(controlObject->origin, 1);

        if (orIdent)
        {
            if (MmsValue_getType(orIdent) == MMS_OCTET_STRING) {
                *orIdentSize  = MmsValue_getOctetStringSize(orIdent);
                return MmsValue_getOctetStringBuffer(orIdent);
            }
        }
    }

    return NULL;
}

int
ControlAction_getCtlNum(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    if (controlObject->ctlNum) {
        return MmsValue_toInt32(controlObject->ctlNum);
    }

    return -1;
}

bool
ControlAction_getSynchroCheck(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return (bool)(controlObject->synchroCheck);
}

bool
ControlAction_getInterlockCheck(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return (bool)(controlObject->interlockCheck);
}

bool
ControlAction_isSelect(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    if (controlObject->isSelect)
        return true;
    else
        return false;
}

ClientConnection
ControlAction_getClientConnection(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return private_IedServer_getClientConnectionByHandle(controlObject->iedServer, controlObject->mmsConnection);
}

DataObject*
ControlAction_getControlObject(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return controlObject->dataObject;
}

uint64_t
ControlAction_getControlTime(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return controlObject->operateTime;
}

Timestamp*
ControlAction_getT(ControlAction self)
{
    ControlObject* controlObject = (ControlObject*) self;

    return &(controlObject->T);
}

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