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

#include "stack_config.h"
#include "libiec61850_platform_includes.h"

#include "iec61850_client.h"

#include "mms_client_connection.h"

#include "ied_connection_private.h"
#include "mms_value_internal.h"

#define DEFAULT_CONNECTION_TIMEOUT 10000
#define DATA_SET_MAX_NAME_LENGTH 64 /* is 32 according to standard! */

typedef struct sICLogicalDevice
{
    char* name;
    LinkedList variables;
} ICLogicalDevice;

struct sClientDataSet
{
    char* dataSetReference; /* data set reference in MMS format */
    MmsValue* dataSetValues; /* MmsValue instance of type MMS_ARRAY */
};

struct sFileDirectoryEntry
{
    char* fileName;
    uint32_t fileSize;
    uint64_t lastModified;
};

const char*
IedClientError_toString(IedClientError err)
{
    switch (err)
    {
    case IED_ERROR_OK:
        return "ok";

    case IED_ERROR_NOT_CONNECTED:
        return "not-connected";

    case IED_ERROR_ALREADY_CONNECTED:
        return "already-connected";

    case IED_ERROR_CONNECTION_LOST:
        return "connection-lost";

    case IED_ERROR_SERVICE_NOT_SUPPORTED:
        return "service-not-supported";

    case IED_ERROR_CONNECTION_REJECTED:
        return "connection-rejected";

    case IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED:
        return "outstanding-call-limit-reached";

    case IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT:
        return "invalid-argument";

    case IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH:
        return "enable-report-failed-due-to-dataset-mismatch";

    case IED_ERROR_OBJECT_REFERENCE_INVALID:
        return "object-reference-invalid";

    case IED_ERROR_UNEXPECTED_VALUE_RECEIVED:
        return "unexpected-value-received";

    case IED_ERROR_TIMEOUT:
        return "timeout";

    case IED_ERROR_ACCESS_DENIED:
        return "access-denied";

    case IED_ERROR_OBJECT_DOES_NOT_EXIST:
        return "object-does-not-exist";

    case IED_ERROR_OBJECT_EXISTS:
        return "object-exists";

    case IED_ERROR_OBJECT_ACCESS_UNSUPPORTED:
        return "object-access-unsupported";

    case IED_ERROR_TYPE_INCONSISTENT:
        return "type-inconsistent";

    case IED_ERROR_TEMPORARILY_UNAVAILABLE:
        return "temporary-unavailable";

    case IED_ERROR_OBJECT_UNDEFINED:
        return "object-undefined";

    case IED_ERROR_INVALID_ADDRESS:
        return "invalid-address";

    case IED_ERROR_HARDWARE_FAULT:
        return "hardware-fault";

    case IED_ERROR_TYPE_UNSUPPORTED:
        return "type-unsupported";

    case IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT:
        return "object-attribute-inconsistent";

    case IED_ERROR_OBJECT_VALUE_INVALID:
        return "object-value-invalid";

    case IED_ERROR_OBJECT_INVALIDATED:
        return "object-invalidated";

    case IED_ERROR_MALFORMED_MESSAGE:
        return "malformed-message";

    case IED_ERROR_OBJECT_CONSTRAINT_CONFLICT:
        return "object-constraint-conflict";

    case IED_ERROR_SERVICE_NOT_IMPLEMENTED:
        return "service-not-implemented";

    default:
        return "unknown-error";
    }
}

IedClientError
iedConnection_mapMmsErrorToIedError(MmsError mmsError)
{
    switch (mmsError) {
    case MMS_ERROR_NONE:
        return IED_ERROR_OK;

    case MMS_ERROR_CONNECTION_LOST:
        return IED_ERROR_CONNECTION_LOST;

    case MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED:
        return IED_ERROR_ACCESS_DENIED;

    case MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT:
        return IED_ERROR_OBJECT_DOES_NOT_EXIST;

    case MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED:
        return IED_ERROR_OBJECT_ACCESS_UNSUPPORTED;

    case MMS_ERROR_DEFINITION_OBJECT_EXISTS:
        return IED_ERROR_OBJECT_EXISTS;

    case MMS_ERROR_DEFINITION_TYPE_INCONSISTENT:
        return IED_ERROR_TYPE_INCONSISTENT;

    case MMS_ERROR_SERVICE_TIMEOUT:
        return IED_ERROR_TIMEOUT;

    case MMS_ERROR_FILE_FILE_NON_EXISTENT:
        return IED_ERROR_OBJECT_DOES_NOT_EXIST;

    case MMS_ERROR_FILE_DUPLICATE_FILENAME:
        return IED_ERROR_OBJECT_EXISTS;

    case MMS_ERROR_FILE_FILE_ACCESS_DENIED:
        return IED_ERROR_ACCESS_DENIED;

    case MMS_ERROR_CONNECTION_REJECTED:
        return IED_ERROR_CONNECTION_REJECTED;

    case MMS_ERROR_ACCESS_OBJECT_VALUE_INVALID:
    	return IED_ERROR_OBJECT_VALUE_INVALID;

    case MMS_ERROR_PARSING_RESPONSE:
        return IED_ERROR_MALFORMED_MESSAGE;

    case MMS_ERROR_OUTSTANDING_CALL_LIMIT:
        return IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;

    case MMS_ERROR_DEFINITION_OBJECT_UNDEFINED:
        return IED_ERROR_OBJECT_UNDEFINED;

    case MMS_ERROR_ACCESS_TEMPORARILY_UNAVAILABLE:
        return IED_ERROR_TEMPORARILY_UNAVAILABLE;

    case MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT:
        return IED_ERROR_OBJECT_CONSTRAINT_CONFLICT;

    default:
        return IED_ERROR_UNKNOWN;
    }
}

IedClientError
iedConnection_mapDataAccessErrorToIedError(MmsDataAccessError mmsError)
{
    switch (mmsError) {

    case DATA_ACCESS_ERROR_NO_RESPONSE:
        return IED_ERROR_TIMEOUT;

    case DATA_ACCESS_ERROR_SUCCESS:
        return IED_ERROR_OK;

    case DATA_ACCESS_ERROR_OBJECT_INVALIDATED:
        return IED_ERROR_OBJECT_INVALIDATED;

    case DATA_ACCESS_ERROR_HARDWARE_FAULT:
        return IED_ERROR_HARDWARE_FAULT;

    case DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE:
        return IED_ERROR_TEMPORARILY_UNAVAILABLE;

    case DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED:
        return IED_ERROR_ACCESS_DENIED;

    case DATA_ACCESS_ERROR_OBJECT_UNDEFINED:
        return IED_ERROR_OBJECT_UNDEFINED;

    case DATA_ACCESS_ERROR_INVALID_ADDRESS:
        return IED_ERROR_INVALID_ADDRESS;

    case DATA_ACCESS_ERROR_TYPE_UNSUPPORTED:
        return IED_ERROR_TYPE_UNSUPPORTED;

    case DATA_ACCESS_ERROR_TYPE_INCONSISTENT:
        return IED_ERROR_TYPE_INCONSISTENT;

    case DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT:
        return IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT;

    case DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED:
        return IED_ERROR_OBJECT_ACCESS_UNSUPPORTED;

    case DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT:
        return IED_ERROR_OBJECT_DOES_NOT_EXIST;

    case DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID:
        return IED_ERROR_OBJECT_VALUE_INVALID;

    default:
        return IED_ERROR_UNKNOWN;
    }
}

IedConnectionOutstandingCall
iedConnection_allocateOutstandingCall(IedConnection self)
{
    Semaphore_wait(self->outstandingCallsLock);

    IedConnectionOutstandingCall call = NULL;

    int i = 0;

    for (i = 0; i < self->maxOutstandingCalled; i++)
    {
        if (self->outstandingCalls[i].used == false)
        {
            self->outstandingCalls[i].used = true;
            call = &(self->outstandingCalls[i]);
            break;
        }
    }

    Semaphore_post(self->outstandingCallsLock);

    return call;
}

void
iedConnection_releaseOutstandingCall(IedConnection self, IedConnectionOutstandingCall call)
{
    Semaphore_wait(self->outstandingCallsLock);

    call->used = false;

    Semaphore_post(self->outstandingCallsLock);
}

IedConnectionOutstandingCall
iedConnection_lookupOutstandingCall(IedConnection self, uint32_t invokeId)
{
    Semaphore_wait(self->outstandingCallsLock);

    IedConnectionOutstandingCall call = NULL;

    int i = 0;

    for (i = 0; i < self->maxOutstandingCalled; i++)
    {
        if ((self->outstandingCalls[i].used) && (self->outstandingCalls[i].invokeId == invokeId))
        {
            call = &(self->outstandingCalls[i]);
            break;
        }
    }

    Semaphore_post(self->outstandingCallsLock);

    return call;
}

static ICLogicalDevice*
ICLogicalDevice_create(char* name)
{
    ICLogicalDevice* self = (ICLogicalDevice*) GLOBAL_CALLOC(1, sizeof(struct sICLogicalDevice));

    if (self)
    {
        self->name = StringUtils_copyString(name);
    }

    return self;
}

static void
ICLogicalDevice_setVariableList(ICLogicalDevice* self, LinkedList variables)
{
    self->variables = variables;
}

static void
ICLogicalDevice_destroy(ICLogicalDevice* self)
{
    if (self)
    {
        GLOBAL_FREEMEM(self->name);

        if (self->variables)
            LinkedList_destroy(self->variables);

        GLOBAL_FREEMEM(self);
    }
}

static ClientDataSet
ClientDataSet_create(const char* dataSetReference)
{
    ClientDataSet self = (ClientDataSet) GLOBAL_CALLOC(1, sizeof(struct sClientDataSet));

    if (self)
    {
        self->dataSetReference = StringUtils_copyString(dataSetReference);

        if (self->dataSetReference)
            StringUtils_replace(self->dataSetReference, '.', '$');

        self->dataSetValues = NULL;
    }

    return self;
}

void
ClientDataSet_destroy(ClientDataSet self)
{
    if (self)
    {
        if (self->dataSetValues)
            MmsValue_delete(self->dataSetValues);

        GLOBAL_FREEMEM(self->dataSetReference);

        GLOBAL_FREEMEM(self);
    }
}

static void
ClientDataSet_setDataSetValues(ClientDataSet self, MmsValue* dataSetValues)
{
    self->dataSetValues = dataSetValues;
}

MmsValue*
ClientDataSet_getValues(ClientDataSet self)
{
    return self->dataSetValues;
}

char*
ClientDataSet_getReference(ClientDataSet self)
{
    return self->dataSetReference;
}

int
ClientDataSet_getDataSetSize(ClientDataSet self)
{
    if (self->dataSetValues)
    {
        return MmsValue_getArraySize(self->dataSetValues);
    }
    else
    {
        return 0;
    }
}

bool
iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj)
{
    int i = 0;

    while (objRef[i] != '/')
    {
        if (objRef[i] != cntrlObj[i])
            return false;

        i++;
    }

    if (cntrlObj[i] != '/')
        return false;

    /* --> LD is equal */

    i++;

    while (objRef[i] != '.')
    {
        if (objRef[i] != cntrlObj[i])
            return false;
        i++;
    }

    int j = i;

    if (cntrlObj[j++] != '$')
        return false;

    /* --> LN is equal */

    if (cntrlObj[j++] != 'C')
        return false;
    if (cntrlObj[j++] != 'O')
        return false;
    if (cntrlObj[j++] != '$')
        return false;

    /* --> FC is ok */

    i++;

    while (objRef[i] != 0)
    {
        if (cntrlObj[j] == 0)
            return false;

        if (objRef[i] == '.')
        {
            if (cntrlObj[j] != '$')
                return false;
        }
        else
        {
            if (objRef[i] != cntrlObj[j])
                return false;
        }

        i++;
        j++;
    }

    if ((cntrlObj[j] == 0) || (cntrlObj[j] == '$'))
        return true;
    else
        return false;
}

static bool
doesReportMatchControlObject(const char* domainName, const char* itemName, const char* objectRef)
{
    int i = 0;

    while (domainName[i] != 0)
    {
        if (domainName[i] != objectRef[i])
            return false;

        i++;
    }

    if (objectRef[i] != '/')
        return false;

    /* --> LD is equal */

    i++;
    int j = 0;

    while (objectRef[i] != '.')
    {
        if (objectRef[i] != itemName[j])
            return false;
        j++;
        i++;
    }

    if (itemName[j++] != '$')
        return false;

    /* --> LN is equal */

    if (itemName[j++] != 'C')
        return false;
    if (itemName[j++] != 'O')
        return false;
    if (itemName[j++] != '$')
        return false;

    /* --> FC is ok */

    i++;

    while (objectRef[i] != 0)
    {
        if (itemName[j] == 0)
            return false;

        if (objectRef[i] == '.')
        {
            if (itemName[j] != '$')
                return false;
        }
        else
        {
            if (objectRef[i] != itemName[j])
                return false;
        }

        i++;
        j++;
    }

    if (itemName[j++] != '$')
        return false;

    /* --> object name is equal */

    if (itemName[j++] != 'O')
        return false;
    if (itemName[j++] != 'p')
        return false;
    if (itemName[j++] != 'e')
        return false;
    if (itemName[j++] != 'r')
        return false;
    if (itemName[j++] != 0)
        return false;

    return true;
}

static void
handleLastApplErrorMessage(IedConnection self, MmsValue* lastApplError)
{
    if (DEBUG_IED_CLIENT)
        printf("IED_CLIENT: received LastApplError\n");

    if ((MmsValue_getType(lastApplError) != MMS_STRUCTURE) || (MmsValue_getArraySize(lastApplError) != 5))
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: LastApplError has wrong type!\n");

        return;
    }

    MmsValue* cntrlObj = MmsValue_getElement(lastApplError, 0);
    MmsValue* error = MmsValue_getElement(lastApplError, 1);
    /* MmsValue* origin = MmsValue_getElement(lastApplError, 2); */
    MmsValue* ctlNum = MmsValue_getElement(lastApplError, 3);
    MmsValue* addCause = MmsValue_getElement(lastApplError, 4);
    if (DEBUG_IED_CLIENT)
        printf("IED_CLIENT:  CntrlObj: %s\n", MmsValue_toString(cntrlObj));

    if (DEBUG_IED_CLIENT)
        printf("IED_CLIENT:  ctlNum: %u\n", MmsValue_toUint32(ctlNum));

    if (DEBUG_IED_CLIENT)
        printf("IED_CLIENT:  addCause: %i\n", MmsValue_toInt32(addCause));

    if (DEBUG_IED_CLIENT)
        printf("IED_CLIENT:  error: %i\n", MmsValue_toInt32(error));

    self->lastApplError.ctlNum = MmsValue_toUint32(ctlNum);
    self->lastApplError.addCause = (ControlAddCause) MmsValue_toInt32(addCause);
    self->lastApplError.error = (ControlLastApplError) MmsValue_toInt32(error);

    Semaphore_wait(self->clientControlsLock);

    LinkedList control = LinkedList_getNext(self->clientControls);

    while (control)
    {
        ControlObjectClient object = (ControlObjectClient) control->data;

        const char* objectRef = ControlObjectClient_getObjectReference(object);

        if (iedConnection_doesControlObjectMatch(objectRef, MmsValue_toString(cntrlObj)))
        {
            ControlObjectClient_setLastApplError(object, self->lastApplError);
        }

        control = LinkedList_getNext(control);
    }

    Semaphore_post(self->clientControlsLock);
}

static void
informationReportHandler(void* parameter, char* domainName, char* variableListName, MmsValue* value,
                         bool isVariableListName)
{
    IedConnection self = (IedConnection)parameter;

    if (value)
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: received information report for %s\n", variableListName);

        if (domainName == NULL)
        {
            if (isVariableListName)
            {
                iedConnection_handleReport(self, value);
            }
            else
            {
                if (strcmp(variableListName, "LastApplError") == 0)
                    handleLastApplErrorMessage(self, value);
                else
                {
                    if (DEBUG_IED_CLIENT)
                        printf("IED_CLIENT: Received unknown variable list report for list: %s\n", variableListName);
                }
            }
        }
        else
        {
            if (DEBUG_IED_CLIENT)
                printf("IED_CLIENT: RCVD CommandTermination for %s/%s\n", domainName, variableListName);

            Semaphore_wait(self->clientControlsLock);

            LinkedList control = LinkedList_getNext(self->clientControls);

            while (control)
            {
                ControlObjectClient object = (ControlObjectClient)control->data;

                const char* objectRef = ControlObjectClient_getObjectReference(object);

                if (doesReportMatchControlObject(domainName, variableListName, objectRef))
                    controlObjectClient_invokeCommandTerminationHandler(object);

                control = LinkedList_getNext(control);
            }

            Semaphore_post(self->clientControlsLock);
        }

        MmsValue_delete(value);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: report for %s/%s: value invalid\n", domainName, variableListName);
    }
}

static void
IedConnection_setState(IedConnection self, IedConnectionState newState)
{
    Semaphore_wait(self->stateMutex);

    if (self->state != newState)
    {
        if (self->connectionStateChangedHandler)
            self->connectionStateChangedHandler(self->connectionStateChangedHandlerParameter, self, newState);
    }

    self->state = newState;

    Semaphore_post(self->stateMutex);
}

static void
mmsConnectionStateChangedHandler(MmsConnection connection, void* parameter, MmsConnectionState newState)
{
    (void)connection;

    IedConnection self = (IedConnection)parameter;

    if (newState == MMS_CONNECTION_STATE_CONNECTED)
    {
        IedConnection_setState(self, IED_STATE_CONNECTED);
    }
    else if (newState == MMS_CONNECTION_STATE_CLOSED)
    {
        IedConnection_setState(self, IED_STATE_CLOSED);

        if (self->connectionCloseHandler)
            self->connectionCloseHandler(self->connectionClosedParameter, self);

        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: Connection closed!\n");
    }
    else if (newState == MMS_CONNECTION_STATE_CLOSING)
    {
        IedConnection_setState(self, IED_STATE_CLOSING);
    }
    else if (newState == MMS_CONNECTION_STATE_CONNECTING)
    {
        IedConnection_setState(self, IED_STATE_CONNECTING);
    }
}

static IedConnection
createNewConnectionObject(TLSConfiguration tlsConfig, bool useThreads)
{
    IedConnection self = (IedConnection) GLOBAL_CALLOC(1, sizeof(struct sIedConnection));

    if (self)
    {
        self->enabledReports = LinkedList_create();
        self->logicalDevices = NULL;
        self->clientControlsLock = Semaphore_create(1);
        self->clientControls = LinkedList_create();


        if (useThreads)
            self->connection = MmsConnection_createSecure(tlsConfig);
        else
            self->connection = MmsConnection_createNonThreaded(tlsConfig);

        self->state = IED_STATE_CLOSED;

        self->stateMutex = Semaphore_create(1);
        self->reportHandlerMutex = Semaphore_create(1);

        self->outstandingCallsLock = Semaphore_create(1);
        self->maxOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
        self->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED, sizeof(struct sIedConnectionOutstandingCall));

        self->connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;

        MmsConnection_setInformationReportHandler(self->connection, informationReportHandler, self);

        MmsConnection_setConnectionStateChangedHandler(self->connection, mmsConnectionStateChangedHandler, self);
    }

    return self;
}

IedConnection
IedConnection_create()
{
    return createNewConnectionObject(NULL, true);
}

IedConnection
IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig)
{
    return createNewConnectionObject(tlsConfig, true);
}

IedConnection
IedConnection_createEx(TLSConfiguration tlsConfig, bool useThreads)
{
    return createNewConnectionObject(tlsConfig, useThreads);
}

bool
IedConnection_tick(IedConnection self)
{
    return MmsConnection_tick(self->connection);
}

void
IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort) 
{
    MmsConnection connection = self->connection;
    IsoConnectionParameters isoP = MmsConnection_getIsoConnectionParameters(connection);
      
    IsoConnectionParameters_setLocalTcpParameters(isoP, localIpAddress, localPort);
}

void
IedConnection_setMaxOutstandingCalls(IedConnection self, int calling, int called)
{
    if (calling < 1)
        calling = 1;

    if (called < 1)
        called = 1;

    if (self->outstandingCalls)
    {
        GLOBAL_FREEMEM(self->outstandingCalls);
    }

    self->maxOutstandingCalled = called;
    self->outstandingCalls = (IedConnectionOutstandingCall)GLOBAL_CALLOC(called, sizeof(struct sIedConnectionOutstandingCall));

    if (self->connection)
    {
        MmsConnnection_setMaxOutstandingCalls(self->connection, calling, called);
    }
}

void
IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs)
{
    self->connectionTimeout = timeoutInMs;
}

void
IedConnection_setRequestTimeout(IedConnection self, uint32_t timeoutInMs)
{
    if (self->connection)
    {
        MmsConnection_setRequestTimeout(self->connection, timeoutInMs);
    }
}

uint32_t
IedConnection_getRequestTimeout(IedConnection self)
{
    if (self->connection) {
        return MmsConnection_getRequestTimeout(self->connection);
    }
    else
        return 0;
}

void
IedConnection_setTimeQuality(IedConnection self, bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision)
{
    uint8_t timeQuality = 0;

    if (clockNotSynchronized)
        timeQuality += 0x20;

    if (clockFailure)
        timeQuality += 0x40;

    if (leapSecondKnown)
        timeQuality += 0x80;

    timeQuality += (subsecondPrecision & 0x1f);

    self->timeQuality = timeQuality;
}

IedConnectionState
IedConnection_getState(IedConnection self)
{
    IedConnectionState state;

    Semaphore_wait(self->stateMutex);
    state = self->state;
    Semaphore_post(self->stateMutex);

    return state;
}

void
IedConnection_installConnectionClosedHandler(IedConnection self, IedConnectionClosedHandler handler,
        void* parameter)
{
    self->connectionCloseHandler = handler;
    self->connectionClosedParameter = parameter;
}

void
IedConnection_connect(IedConnection self, IedClientError* error, const char* hostname, int tcpPort)
{
    if (IedConnection_getState(self) != IED_STATE_CONNECTED)
    {
        MmsError mmsError;

        MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout);

        if (MmsConnection_connect(self->connection, &mmsError, hostname, tcpPort))
        {
            *error = IED_ERROR_OK;
            IedConnection_setState(self, IED_STATE_CONNECTED);
        }
        else
        {
            IedConnection_setState(self, IED_STATE_CLOSED);
            *error = iedConnection_mapMmsErrorToIedError(mmsError);
        }
    }
    else
        *error = IED_ERROR_ALREADY_CONNECTED;
}

void
IedConnection_installStateChangedHandler(IedConnection self, IedConnection_StateChangedHandler handler, void* parameter)
{
    self->connectionStateChangedHandler = handler;
    self->connectionStateChangedHandlerParameter = parameter;
}

void
IedConnection_connectAsync(IedConnection self, IedClientError* error, const char* hostname, int tcpPort)
{
    if (IedConnection_getState(self) != IED_STATE_CONNECTED)
    {
        MmsError mmsError = MMS_ERROR_NONE;

        MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout);

        MmsConnection_connectAsync(self->connection, &mmsError, hostname, tcpPort);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
    else
        *error = IED_ERROR_ALREADY_CONNECTED;
}

void
IedConnection_abort(IedConnection self, IedClientError* error)
{
    if (IedConnection_getState(self) == IED_STATE_CONNECTED)
    {
        IedConnection_setState(self, IED_STATE_CLOSING);

        MmsError mmsError;

        MmsConnection_abort(self->connection, &mmsError);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
    else
        *error = IED_ERROR_NOT_CONNECTED;
}

void
IedConnection_abortAsync(IedConnection self, IedClientError* error)
{
    if (IedConnection_getState(self) == IED_STATE_CONNECTED)
    {
        MmsError mmsError;

        MmsConnection_abortAsync(self->connection, &mmsError);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
    else
        *error = IED_ERROR_NOT_CONNECTED;
}

void
IedConnection_release(IedConnection self, IedClientError* error)
{
    if (IedConnection_getState(self) == IED_STATE_CONNECTED)
    {
        MmsError mmsError;

        MmsConnection_conclude(self->connection, &mmsError);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
    else
        *error = IED_ERROR_NOT_CONNECTED;
}

void
IedConnection_releaseAsync(IedConnection self, IedClientError* error)
{
    if (IedConnection_getState(self) == IED_STATE_CONNECTED)
    {
        MmsError mmsError;

        MmsConnection_concludeAsync(self->connection, &mmsError, NULL, NULL);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
    else
        *error = IED_ERROR_NOT_CONNECTED;
}

void
IedConnection_close(IedConnection self)
{
    if (IedConnection_getState(self) == IED_STATE_CONNECTED)
    {
        IedConnection_setState(self, IED_STATE_CLOSING);
        MmsConnection_close(self->connection);
    }
}

static void
deleteControlObjectClients(IedConnection self)
{
    while (LinkedList_size(self->clientControls) > 0)
    {
        LinkedList elem = LinkedList_getNext(self->clientControls);

        if (elem)
        {
            ControlObjectClient coClient = (ControlObjectClient)LinkedList_getData(elem);

            if (coClient)
            {
                ControlObjectClient_destroy(coClient);
            }
        }
    }
}

void
IedConnection_destroy(IedConnection self)
{
    if (self)
    {
        IedConnection_close(self);

        MmsConnection_destroy(self->connection);

        if (self->logicalDevices)
            LinkedList_destroyDeep(self->logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy);

        if (self->enabledReports)
            LinkedList_destroyDeep(self->enabledReports, (LinkedListValueDeleteFunction) ClientReport_destroy);

        GLOBAL_FREEMEM(self->outstandingCalls);

        deleteControlObjectClients(self);

        LinkedList_destroyStatic(self->clientControls);

        Semaphore_destroy(self->clientControlsLock);
        Semaphore_destroy(self->outstandingCallsLock);
        Semaphore_destroy(self->stateMutex);
        Semaphore_destroy(self->reportHandlerMutex);

        GLOBAL_FREEMEM(self);
    }
}

LinkedList
IedConnection_getLogicalDeviceVariables(IedConnection self, IedClientError* error, const char* ldName)
{
    MmsError err = MMS_ERROR_NONE;

    LinkedList result = MmsConnection_getDomainVariableNames(self->connection, &err, ldName);

    *error = iedConnection_mapMmsErrorToIedError(err);

    return result;
}

LinkedList
IedConnection_getLogicalDeviceDataSets(IedConnection self, IedClientError* error, const char* ldName)
{
    MmsError err = MMS_ERROR_NONE;

    LinkedList result = MmsConnection_getDomainVariableListNames(self->connection, &err, ldName);

    *error = iedConnection_mapMmsErrorToIedError(err);

    return result;
}

MmsVariableSpecification*
IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc)
{
    char domainIdBuffer[65];
    char itemIdBuffer[65];

    char* domainId;
    char* itemId;

    MmsError mmsError;
    MmsVariableSpecification* varSpec = NULL;

    domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        goto cleanup_and_exit;
    }

    varSpec =
            MmsConnection_getVariableAccessAttributes(self->connection, &mmsError, domainId, itemId);

    if (varSpec)
        *error = IED_ERROR_OK;
    else
        *error = iedConnection_mapMmsErrorToIedError(mmsError);

cleanup_and_exit:

    return varSpec;
}

static void
getAccessAttrHandler(uint32_t invokeId, void* parameter, MmsError err, MmsVariableSpecification* typeSpec)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GetVariableSpecificationHandler handler =  (IedConnection_GetVariableSpecificationHandler) call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), typeSpec);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_getVariableSpecificationAsync(IedConnection self, IedClientError* error, const char* dataAttributeReference,
        FunctionalConstraint fc, IedConnection_GetVariableSpecificationHandler handler, void* parameter)
{
    uint32_t invokeId = 0;

    char domainIdBuffer[65];
    char itemIdBuffer[65];

    char* domainId;
    char* itemId;

    MmsError err;

    domainId = MmsMapping_getMmsDomainFromObjectReference(dataAttributeReference, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(dataAttributeReference, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsConnection_getVariableAccessAttributesAsync(self->connection, &(call->invokeId), &err, domainId, itemId, getAccessAttrHandler, self);

    invokeId = call->invokeId;

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
        iedConnection_releaseOutstandingCall(self, call);

    return invokeId;
}

static void
getNameListHandler(uint32_t invokeId, void* parameter, MmsError mmsError, LinkedList nameList, bool moreFollows)
{
    IedConnection self = (IedConnection)parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GetNameListHandler handler = (IedConnection_GetNameListHandler)call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(mmsError), nameList,
                moreFollows);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_getServerDirectoryAsync(IedConnection self, IedClientError* error, const char* continueAfter,
                                      LinkedList result, IedConnection_GetNameListHandler handler, void* parameter)
{
    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsError err = MMS_ERROR_NONE;

    MmsConnection_getDomainNamesAsync(self->connection, &(call->invokeId), &err, continueAfter, result,
                                      getNameListHandler, self);

    if (err != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(err);

        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }
    else
    {
        *error = IED_ERROR_OK;
    }

    return call->invokeId;
}

uint32_t
IedConnection_getLogicalDeviceVariablesAsync(IedConnection self, IedClientError* error, const char* ldName,
                                             const char* continueAfter, LinkedList result,
                                             IedConnection_GetNameListHandler handler, void* parameter)
{
    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsError err = MMS_ERROR_NONE;

    MmsConnection_getDomainVariableNamesAsync(self->connection, &(call->invokeId), &err, ldName, continueAfter, result,
                                              getNameListHandler, self);

    if (err != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(err);

        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }
    else
    {
        *error = IED_ERROR_OK;
    }

    return call->invokeId;
}

uint32_t
IedConnection_getLogicalDeviceDataSetsAsync(IedConnection self, IedClientError* error, const char* ldName, const char* continueAfter, LinkedList result,
        IedConnection_GetNameListHandler handler, void* parameter)
{
    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsError err = MMS_ERROR_NONE;

    MmsConnection_getDomainVariableListNamesAsync(self->connection, &(call->invokeId), &err, ldName, continueAfter, result, getNameListHandler, self);

    if (err != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(err);

        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }
    else {
        *error = IED_ERROR_OK;
    }

    return call->invokeId;
}

static void
readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsValue* value)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_ReadObjectHandler handler =  (IedConnection_ReadObjectHandler) call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), value);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const char* objRef, FunctionalConstraint fc,
        IedConnection_ReadObjectHandler handler, void* parameter)
{
    *error = IED_ERROR_OK;

    char domainIdBuffer[65];
    char itemIdBuffer[65];

    char* domainId;
    char* itemId;

    domainId = MmsMapping_getMmsDomainFromObjectReference(objRef, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(objRef, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsError err = MMS_ERROR_NONE;

    /* check if item ID contains an array "(..)" */
    char* brace = strchr(itemId, '(');

    if (brace)
    {
        char* secondBrace = strchr(brace, ')');

        if (secondBrace)
        {
            char* endPtr;

            int index = (int) strtol(brace + 1, &endPtr, 10);

            if (endPtr == secondBrace)
            {
                char* component = NULL;

                if (strlen(secondBrace + 1) > 1)
                    component = secondBrace + 2; /* skip "." after array element specifier */

                *brace = 0;

                MmsConnection_readSingleArrayElementWithComponentAsync(self->connection, &(call->invokeId), &err, domainId, itemId, index, component, readObjectHandlerInternal, self);
            }
            else
                *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
        }
        else
            *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
    }
    else
        MmsConnection_readVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, readObjectHandlerInternal, self);

    if ((err != MMS_ERROR_NONE) || (*error != IED_ERROR_OK))
    {
        if (err != MMS_ERROR_NONE)
        {
            *error = iedConnection_mapMmsErrorToIedError(err);
        }

        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }

    return call->invokeId;
}

MmsValue*
IedConnection_readObject(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc)
{
    char domainIdBuffer[65];
    char itemIdBuffer[65];
    MmsValue* value = NULL;

    char* domainId;
    char* itemId;

    domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    MmsError mmsError = MMS_ERROR_NONE;

    /* check if item ID contains an array "(..)" */
    char* brace = strchr(itemId, '(');

    if (brace)
    {
        char* secondBrace = strchr(brace, ')');

        if (secondBrace)
        {
            char* endPtr;

            int index = (int) strtol(brace + 1, &endPtr, 10);

            if (endPtr == secondBrace)
            {
                char* component = NULL;

                if (strlen(secondBrace + 1) > 1)
                    component = secondBrace + 2; /* skip "." after array element specifier */

                *brace = 0;

                value = MmsConnection_readSingleArrayElementWithComponent(self->connection, &mmsError, domainId, itemId, index, component);
            }
            else
                *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
        }
        else
            *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
    }
    else
        value = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId);

    if (value)
        *error = IED_ERROR_OK;
    else
        *error = iedConnection_mapMmsErrorToIedError(mmsError);

    return value;
}

bool
IedConnection_readBooleanValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    bool retVal = false;

    if (value)
    {
        if (MmsValue_getType(value) == MMS_BOOLEAN)
            retVal = MmsValue_getBoolean(value);
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

float
IedConnection_readFloatValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    float retVal = 0.f;

    if (value)
    {
        if (MmsValue_getType(value) == MMS_FLOAT)
            retVal = MmsValue_toFloat(value);
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

char*
IedConnection_readStringValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    char* retVal = NULL;

    if (value)
    {
        if ((MmsValue_getType(value) == MMS_VISIBLE_STRING) || (MmsValue_getType(value) == MMS_STRING))
            retVal = StringUtils_copyString(MmsValue_toString(value));
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

int32_t
IedConnection_readInt32Value(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    int32_t retVal = 0;

    if (value)
    {
        if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED))
            retVal = MmsValue_toInt32(value);
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

uint32_t
IedConnection_readUnsigned32Value(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    uint32_t retVal = 0;

    if (value)
    {
        if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED))
            retVal = MmsValue_toUint32(value);
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

int64_t
IedConnection_readInt64Value(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    int64_t retVal = 0;

    if (value)
    {
        if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED))
            retVal = MmsValue_toInt64(value);
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return retVal;
}

Timestamp*
IedConnection_readTimestampValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc,
        Timestamp* timeStamp)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    Timestamp* retVal = timeStamp;

    if (value)
    {
        if (MmsValue_getType(value) == MMS_UTC_TIME)
        {
            if (retVal == NULL)
                retVal = (Timestamp*) GLOBAL_MALLOC(sizeof(Timestamp));

            if (retVal)
                memcpy(retVal->val, value->value.utcTime, 8);
        }
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);

    }

    return retVal;
}

Quality
IedConnection_readQualityValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc)
{
    MmsValue* value = IedConnection_readObject(self, error, objectReference, fc);

    Quality quality = QUALITY_VALIDITY_GOOD;

    if (value)
    {
        if ((MmsValue_getType(value) == MMS_BIT_STRING) && (MmsValue_getBitStringSize(value) == 13)) {
            quality = Quality_fromMmsValue(value);
        }
        else
        {
            if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
                *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value));
            else
                *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
        }

        MmsValue_delete(value);
    }

    return quality;
}

void
IedConnection_writeObject(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, MmsValue* value)
{
    char domainIdBuffer[65];
    char itemIdBuffer[65];

    char* domainId;
    char* itemId;

    domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return;
    }

    MmsError mmsError;

    /* check if item ID contains an array "(..)" */
    char* brace = strchr(itemId, '(');

    if (brace)
    {
        char* secondBrace = strchr(brace, ')');

        if (secondBrace)
        {
            char* endPtr;

            int index = (int) strtol(brace + 1, &endPtr, 10);

            if (endPtr == secondBrace)
            {
                char* component = NULL;

                if (strlen(secondBrace + 1) > 1)
                    component = secondBrace + 2; /* skip "." after array element specifier */

                *brace = 0;

                MmsConnection_writeSingleArrayElementWithComponent(self->connection, &mmsError, domainId, itemId, index, component, value);

                *error = iedConnection_mapMmsErrorToIedError(mmsError);
            }
            else
                *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
        }
        else
            *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
    }
    else
    {
        MmsConnection_writeVariable(self->connection, &mmsError, domainId, itemId, value);

        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
}

static void
writeVariableHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAccessError accessError)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GenericServiceHandler handler =  (IedConnection_GenericServiceHandler) call->callback;

        IedClientError iedError = iedConnection_mapMmsErrorToIedError(err);

        if (iedError == IED_ERROR_OK)
            iedError = iedConnection_mapDataAccessErrorToIedError(accessError);

        handler(invokeId, call->callbackParameter, iedError);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, MmsValue* value, IedConnection_GenericServiceHandler handler, void* parameter)
{
    *error = IED_ERROR_OK;

    char domainIdBuffer[65];
    char itemIdBuffer[65];

    char* domainId;
    char* itemId;

    domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
    itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);

    if ((domainId == NULL) || (itemId == NULL))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->invokeId = 0;

    MmsError err;

    /* check if item ID contains an array "(..)" */
    char* brace = strchr(itemId, '(');

    if (brace)
    {
        char* secondBrace = strchr(brace, ')');

        if (secondBrace)
        {
            char* endPtr;

            int index = (int) strtol(brace + 1, &endPtr, 10);

            if (endPtr == secondBrace)
            {
                char* component = NULL;

                if (strlen(secondBrace + 1) > 1)
                    component = secondBrace + 2; /* skip "." after array element specifier */

                *brace = 0;

                MmsConnection_writeSingleArrayElementWithComponentAsync(self->connection, &(call->invokeId), &err, domainId, itemId, index, component, value,
                        writeVariableHandler, self);

                *error = iedConnection_mapMmsErrorToIedError(err);
            }
            else
                *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
        }
        else
            *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
    }
    else
    {
        MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, value, writeVariableHandler, self);

        *error = iedConnection_mapMmsErrorToIedError(err);
    }

    if (*error != IED_ERROR_OK)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}

void
IedConnection_writeBooleanValue(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, bool value)
{
    MmsValue mmsValue;
    mmsValue.deleteValue = 0;
    mmsValue.type = MMS_BOOLEAN;
    mmsValue.value.boolean = value;

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_writeInt32Value(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, int32_t value)
{
    uint8_t valueBuffer[4];

    Asn1PrimitiveValue pVal;
    pVal.maxSize = 4;
    pVal.size = 4;
    pVal.octets = valueBuffer;

    MmsValue mmsValue;
    mmsValue.type = MMS_INTEGER;
    mmsValue.deleteValue = 0;
    mmsValue.value.integer = &pVal;

    MmsValue_setInt32(&mmsValue, value);

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_writeUnsigned32Value(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, uint32_t value)
{
    uint8_t valueBuffer[4];

    Asn1PrimitiveValue pVal;
    pVal.maxSize = 4;
    pVal.size = 4;
    pVal.octets = valueBuffer;

    MmsValue mmsValue;
    mmsValue.type = MMS_UNSIGNED;
    mmsValue.deleteValue = 0;
    mmsValue.value.integer = &pVal;

    MmsValue_setUint32(&mmsValue, value);

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_writeFloatValue(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, float value)
{
    MmsValue mmsValue;
    mmsValue.type = MMS_FLOAT;
    mmsValue.value.floatingPoint.exponentWidth = 8;
    mmsValue.value.floatingPoint.formatWidth = 32;
    memcpy(mmsValue.value.floatingPoint.buf, (uint8_t*) &value, sizeof(value));

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_writeOctetString(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, uint8_t* value, int valueLength)
{
    MmsValue mmsValue;
    mmsValue.type = MMS_OCTET_STRING;
    mmsValue.value.octetString.buf = value;
    mmsValue.value.octetString.size = valueLength;

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_writeVisibleStringValue(IedConnection self, IedClientError* error, const char* objectReference,
        FunctionalConstraint fc, char* value)
{
    MmsValue mmsValue;
    mmsValue.deleteValue = 0;
    mmsValue.type = MMS_VISIBLE_STRING;
    mmsValue.value.visibleString.buf = value;

    IedConnection_writeObject(self, error, objectReference, fc, &mmsValue);
}

void
IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error)
{
    MmsError mmsError = MMS_ERROR_NONE;

    if (error)
        *error = IED_ERROR_OK;

    LinkedList logicalDeviceNames = MmsConnection_getDomainNames(self->connection, &mmsError);

    if (logicalDeviceNames)
    {
        if (self->logicalDevices)
        {
            LinkedList_destroyDeep(self->logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy);
            self->logicalDevices = NULL;
        }

        LinkedList logicalDevice = LinkedList_getNext(logicalDeviceNames);

        LinkedList logicalDevices = LinkedList_create();

        while (logicalDevice)
        {
            char* name = (char*) logicalDevice->data;

            LinkedList variables = MmsConnection_getDomainVariableNames(self->connection,
                    &mmsError, name);

            if (variables)
            {
                ICLogicalDevice* icLogicalDevice = ICLogicalDevice_create(name);

                ICLogicalDevice_setVariableList(icLogicalDevice, variables);

                LinkedList_add(logicalDevices, icLogicalDevice);
            }
            else
            {
                if (error)
                    *error = iedConnection_mapMmsErrorToIedError(mmsError);
                break;
            }

            logicalDevice = LinkedList_getNext(logicalDevice);
        }

        if (mmsError != MMS_ERROR_NONE)
        {
            LinkedList_destroyDeep(logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy);
        }
        else
        {
            self->logicalDevices = logicalDevices;
        }

        LinkedList_destroy(logicalDeviceNames);
    }
    else
        if (error) *error = iedConnection_mapMmsErrorToIedError(mmsError);
}

LinkedList /*<char*>*/
IedConnection_getLogicalDeviceList(IedConnection self, IedClientError* error)
{
    *error = IED_ERROR_OK;

    if (self->logicalDevices == NULL)
    {
        IedConnection_getDeviceModelFromServer(self, error);

        if (*error != IED_ERROR_OK)
            return NULL;
    }

    if (self->logicalDevices)
    {
        LinkedList logicalDevice = LinkedList_getNext(self->logicalDevices);

        LinkedList logicalDeviceList = LinkedList_create();

        while (logicalDevice)
        {
            ICLogicalDevice* icLogicalDevice = (ICLogicalDevice*) logicalDevice->data;

            char* logicalDeviceName = StringUtils_copyString(icLogicalDevice->name);

            LinkedList_add(logicalDeviceList, logicalDeviceName);

            logicalDevice = LinkedList_getNext(logicalDevice);
        }

        if (error)
            *error = IED_ERROR_OK;

        return logicalDeviceList;
    }
    else
    {
        if (error)
            *error = IED_ERROR_UNKNOWN;

        return NULL;
    }
}

static void
mmsFileDirectoryHandler(void* parameter, char* filename, uint32_t size, uint64_t lastModified)
{
    LinkedList fileNames = (LinkedList) parameter;

    FileDirectoryEntry newDirectoryEntry = FileDirectoryEntry_create(filename, size, lastModified);

    LinkedList_add(fileNames, (void*) newDirectoryEntry);
}

LinkedList /*<FileDirectoryEntry>*/
IedConnection_getFileDirectory(IedConnection self, IedClientError* error, const char* directoryName)
{
    *error = IED_ERROR_OK;

    MmsError mmsError = MMS_ERROR_NONE;

    LinkedList fileNames = LinkedList_create();

    char* continueAfter = NULL;

    bool moreFollows = false;

    do
    {
        moreFollows =
                MmsConnection_getFileDirectory(self->connection, &mmsError, directoryName, continueAfter,
                        mmsFileDirectoryHandler, fileNames);

        if (mmsError != MMS_ERROR_NONE)
        {
            *error = iedConnection_mapMmsErrorToIedError(mmsError);
            LinkedList_destroyDeep(fileNames, (LinkedListValueDeleteFunction) FileDirectoryEntry_destroy);

            return NULL;
        }

        if (moreFollows)
        {
            FileDirectoryEntry lastDirectoryEntry = (FileDirectoryEntry)
                    LinkedList_getData(LinkedList_getLastElement(fileNames));

            continueAfter = lastDirectoryEntry->fileName;
        }

    } while (moreFollows == true);

    return fileNames;
}

LinkedList /*<FileDirectoryEntry>*/
IedConnection_getFileDirectoryEx(IedConnection self, IedClientError* error, const char* directoryName, const char* continueAfter,
        bool* moreFollows)
{
    *error = IED_ERROR_OK;

    MmsError mmsError = MMS_ERROR_NONE;

    LinkedList fileNames = LinkedList_create();

    bool moreFollowsInternal =  MmsConnection_getFileDirectory(self->connection, &mmsError, directoryName, continueAfter,
                                    mmsFileDirectoryHandler, fileNames);

    if (mmsError != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
        LinkedList_destroyDeep(fileNames, (LinkedListValueDeleteFunction) FileDirectoryEntry_destroy);

        return NULL;
    }

    if (moreFollows)
        *moreFollows = moreFollowsInternal;

    return fileNames;
}

static void
fileDirectoryHandlerEx(uint32_t invokeId, void* parameter, MmsError err, char* filename, uint32_t size, uint64_t lastModfified,
        bool moreFollows)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        if (call->specificParameter2.getFileDirectory.cont)
        {
            IedConnection_FileDirectoryEntryHandler handler = (IedConnection_FileDirectoryEntryHandler) call->callback;

            call->specificParameter2.getFileDirectory.cont =
                    handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), filename, size, lastModfified, moreFollows);
        }

        if (filename == NULL)
            iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_getFileDirectoryAsyncEx(IedConnection self, IedClientError* error, const char* directoryName, const char* continueAfter,
        IedConnection_FileDirectoryEntryHandler handler, void* parameter)
{
    MmsError err = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
       *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
       return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->specificParameter2.getFileDirectory.cont = true;

    MmsConnection_getFileDirectoryAsync(self->connection, &(call->invokeId), &err, directoryName, continueAfter,
            fileDirectoryHandlerEx, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}

struct sClientProvidedFileReadHandler {
    IedClientGetFileHandler handler;
    void* handlerParameter;
    bool retVal;
    uint32_t byteReceived;
};

static void
mmsFileReadHandler(void* parameter, int32_t frsmId, uint8_t* buffer, uint32_t bytesReceived)
{
    (void)frsmId;

    struct sClientProvidedFileReadHandler* handler = (struct sClientProvidedFileReadHandler*) parameter;

    handler->retVal = handler->handler(handler->handlerParameter, buffer, bytesReceived);

    handler->byteReceived += bytesReceived;
}

uint32_t
IedConnection_getFile(IedConnection self, IedClientError* error, const char* fileName, IedClientGetFileHandler handler,
        void* handlerParameter)
{
    *error = IED_ERROR_OK;

    MmsError mmsError;

    uint32_t fileSize;

    int32_t frsmId =
            MmsConnection_fileOpen(self->connection, &mmsError, fileName, 0, &fileSize, NULL);

    if (mmsError != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
        return 0;
    }

    struct sClientProvidedFileReadHandler clientFileReadHandler;
    clientFileReadHandler.handler = handler;
    clientFileReadHandler.handlerParameter = handlerParameter;
    clientFileReadHandler.retVal = true;
    clientFileReadHandler.byteReceived = 0;

    while (true)
    {
        bool moreFollows =
                MmsConnection_fileRead(self->connection, &mmsError, frsmId, mmsFileReadHandler,
                        &clientFileReadHandler);

        if (mmsError != MMS_ERROR_NONE)
        {
            *error = iedConnection_mapMmsErrorToIedError(mmsError);
            return 0;
        }

        if (clientFileReadHandler.retVal == false)
        {
            *error = IED_ERROR_UNKNOWN;
            break;
        }

        if (moreFollows == false)
            break;
    }

    MmsConnection_fileClose(self->connection, &mmsError, frsmId);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

    return clientFileReadHandler.byteReceived;
}

static void
mmsConnectionFileCloseHandler (uint32_t invokeId, void* parameter, MmsError mmsError, bool success)
{
    (void)success;

    if (mmsError != MMS_ERROR_NONE)
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: failed to close file error: %i (mms-error: %i)\n", iedConnection_mapMmsErrorToIedError(mmsError), mmsError);
    }

    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

static void
mmsConnectionFileReadHandler (uint32_t invokeId, void* parameter, MmsError mmsError, int32_t frsmId, uint8_t* buffer, uint32_t byteReceived,
        bool moreFollows)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GetFileAsyncHandler handler =  (IedConnection_GetFileAsyncHandler) call->callback;

        if (mmsError != MMS_ERROR_NONE)
        {
            IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

            handler(call->specificParameter2.getFileInfo.originalInvokeId, call->callbackParameter, err, invokeId, NULL, 0, false);

            if (mmsError != MMS_ERROR_SERVICE_TIMEOUT)
            {
                /* close file */
                MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self);

                if (mmsError != MMS_ERROR_NONE)
                    iedConnection_releaseOutstandingCall(self, call);
            }
            else
            {
                if (DEBUG_IED_CLIENT)
                    printf("IED_CLIENT: getFile timeout -> stop download\n");

                iedConnection_releaseOutstandingCall(self, call);
            }
        }
        else
        {
            bool cont = handler(call->specificParameter2.getFileInfo.originalInvokeId, call->callbackParameter, IED_ERROR_OK, invokeId, buffer, byteReceived, moreFollows);

            if ((moreFollows == false) || (cont == false))
            {
                /* close file */
                MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self);

                if (mmsError != MMS_ERROR_NONE)
                    iedConnection_releaseOutstandingCall(self, call);
            }
            else
            {
                /* send next read request */

                MmsConnection_fileReadAsync(self->connection, &(call->invokeId), &mmsError, frsmId,
                        mmsConnectionFileReadHandler, self);

                if (mmsError != MMS_ERROR_NONE)
                {
                    IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

                    handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false);

                    /* close file */
                    MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self);

                    if (mmsError != MMS_ERROR_NONE)
                    {
                        iedConnection_releaseOutstandingCall(self, call);
                    }
                }
            }
        }
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

static void
mmsConnectionFileOpenHandler (uint32_t invokeId, void* parameter, MmsError mmsError, int32_t frsmId, uint32_t fileSize, uint64_t lastModified)
{
    (void)fileSize;
    (void)lastModified;

    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GetFileAsyncHandler handler =  (IedConnection_GetFileAsyncHandler) call->callback;

        call->specificParameter2.getFileInfo.originalInvokeId = invokeId;

        if (mmsError != MMS_ERROR_NONE)
        {
            IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

            handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false);

            iedConnection_releaseOutstandingCall(self, call);
        }
        else
        {
            call->specificParameter2.getFileInfo.originalInvokeId = invokeId;
            MmsConnection_fileReadAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileReadHandler, self);

            if (mmsError != MMS_ERROR_NONE)
            {
                IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

                handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false);

                /* close file */
                MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self);

                if (mmsError != MMS_ERROR_NONE)
                    iedConnection_releaseOutstandingCall(self, call);
            }
        }
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}


uint32_t
IedConnection_getFileAsync(IedConnection self, IedClientError* error, const char* fileName, IedConnection_GetFileAsyncHandler handler,
        void* parameter)
{
    MmsError err = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
       *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
       return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsConnection_fileOpenAsync(self->connection, &(call->invokeId), &err, fileName, 0, mmsConnectionFileOpenHandler, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}

void
IedConnection_setFilestoreBasepath(IedConnection self, const char* basepath)
{
    /* simply pass the call to MMS client API */
    MmsConnection_setFilestoreBasepath(self->connection, basepath);
}

void
IedConnection_setFile(IedConnection self, IedClientError* error, const char* sourceFilename, const char* destinationFilename)
{
#if (MMS_OBTAIN_FILE_SERVICE == 1)
    *error = IED_ERROR_OK;

    MmsError mmsError;

    MmsConnection_obtainFile(self->connection, &mmsError, sourceFilename, destinationFilename);

    if (mmsError != MMS_ERROR_NONE) {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
    }
#else
    *error = IED_ERROR_SERVICE_NOT_IMPLEMENTED;
#endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */
}

static void
deleteFileAndSetFileHandler (uint32_t invokeId, void* parameter, MmsError mmsError, bool success)
{
    (void)success;

    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GenericServiceHandler handler =  (IedConnection_GenericServiceHandler) call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(mmsError));

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_setFileAsync(IedConnection self, IedClientError* error, const char* sourceFilename, const char* destinationFilename,
        IedConnection_GenericServiceHandler handler, void* parameter)
{
    MmsError err = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
       *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
       return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsConnection_obtainFileAsync(self->connection, &(call->invokeId), &err, sourceFilename, destinationFilename, deleteFileAndSetFileHandler, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}

void
IedConnection_deleteFile(IedConnection self, IedClientError* error, const char* fileName)
{
    *error = IED_ERROR_OK;

    MmsError mmsError;

    MmsConnection_fileDelete(self->connection, &mmsError, fileName);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);
}

uint32_t
IedConnection_deleteFileAsync(IedConnection self, IedClientError* error, const char* fileName,
        IedConnection_GenericServiceHandler handler, void* parameter)
{
    MmsError err = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
       *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
       return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsConnection_fileDeleteAsync(self->connection, &(call->invokeId), &err, fileName, deleteFileAndSetFileHandler, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}


LinkedList /*<char*>*/
IedConnection_getServerDirectory(IedConnection self, IedClientError* error, bool getFileNames)
{
    if (getFileNames)
        return IedConnection_getFileDirectory(self, error, NULL);
    else
        return IedConnection_getLogicalDeviceList(self, error);
}

LinkedList /*<char*>*/
IedConnection_getLogicalDeviceDirectory(IedConnection self, IedClientError* error,
        const char* logicalDeviceName)
{
    *error = IED_ERROR_OK;

    if (self->logicalDevices == NULL)
        IedConnection_getDeviceModelFromServer(self, error);

    if (*error != IED_ERROR_OK)
        return NULL;

    LinkedList logicalDevice = LinkedList_getNext(self->logicalDevices);

    while (logicalDevice)
    {
        ICLogicalDevice* device = (ICLogicalDevice*) logicalDevice->data;

        if (strcmp(device->name, logicalDeviceName) == 0)
        {
            LinkedList logicalNodeNames = LinkedList_create();

            LinkedList variable = LinkedList_getNext(device->variables);

            while (variable)
            {
                char* variableName = (char*) variable->data;

                if (strchr(variableName, '$') == NULL)
                    LinkedList_add(logicalNodeNames, StringUtils_copyString((char*) variable->data));

                variable = LinkedList_getNext(variable);
            }

            return logicalNodeNames;
        }

        logicalDevice = LinkedList_getNext(logicalDevice);
    }

    *error = IED_ERROR_OBJECT_REFERENCE_INVALID;

    return NULL;
}

static bool
addToStringSet(LinkedList set, char* string)
{
    LinkedList element = set;

    while (LinkedList_getNext(element))
    {
        if (strcmp((char*) LinkedList_getNext(element)->data, string) == 0)
            return false;

        element = LinkedList_getNext(element);
    }

    LinkedList_insertAfter(element, string);
    return true;
}

static void
addVariablesWithFc(char* fc, char* lnName, LinkedList variables, LinkedList lnDirectory)
{
    LinkedList variable = LinkedList_getNext(variables);

    while (variable)
    {
        char* variableName = (char*) variable->data;

        char* fcPos = strchr(variableName, '$');

        if (fcPos)
        {
            if (memcmp(fcPos + 1, fc, 2) != 0)
                goto next_element;

            int lnNameLen = (int)(fcPos - variableName);

            if (strncmp(variableName, lnName, lnNameLen) == 0)
            {
                char* fcEndPos = strchr(fcPos + 1, '$');

                if (fcEndPos)
                {
                    char* nameEndPos = strchr(fcEndPos + 1, '$');

                    if (nameEndPos == NULL)
                        addToStringSet(lnDirectory, StringUtils_copyString(fcEndPos + 1));
                }
            }
        }

        next_element:

        variable = LinkedList_getNext(variable);
    }
}

static LinkedList
getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const char* logicalDeviceName,
        const char* logicalNodeName)
{
    MmsConnection mmsCon = self->connection;

    MmsError mmsError;

    LinkedList journals = MmsConnection_getDomainJournals(mmsCon, &mmsError, logicalDeviceName);

    if (mmsError != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
        return NULL;
    }

    LinkedList logs = LinkedList_create();

    LinkedList journal = LinkedList_getNext(journals);

    while (journal)
    {
        char* journalName = (char*) LinkedList_getData(journal);

        char* logName = strchr(journalName, '$');

        if (logName)
        {
            logName[0] = 0;
            logName += 1;

            if (strcmp(journalName, logicalNodeName) == 0)
            {
                char* log = StringUtils_copyString(logName);
                LinkedList_add(logs, (void*) log);
            }
        }

        journal = LinkedList_getNext(journal);
    }

    LinkedList_destroy(journals);

    return logs;
}

static LinkedList
getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const char* logicalDeviceName,
        const char* logicalNodeName)
{
    MmsConnection mmsCon = self->connection;

    MmsError mmsError;

    LinkedList dataSets = MmsConnection_getDomainVariableListNames(mmsCon, &mmsError, logicalDeviceName);

    if (mmsError != MMS_ERROR_NONE)
    {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
        return NULL;
    }

    LinkedList lnDataSets = LinkedList_create();

    LinkedList dataSet = LinkedList_getNext(dataSets);

    while (dataSet)
    {
        char* dataSetName = (char*) LinkedList_getData(dataSet);

        char* lnDataSetName = strchr(dataSetName, '$');

        if (lnDataSetName)
        {
            lnDataSetName[0] = 0;
            lnDataSetName += 1;

            if (strcmp(dataSetName, logicalNodeName) == 0)
            {
                char* lnDataSet = StringUtils_copyString(lnDataSetName);
                LinkedList_add(lnDataSets, (void*) lnDataSet);
            }
        }

        dataSet = LinkedList_getNext(dataSet);
    }

    LinkedList_destroy(dataSets);

    return lnDataSets;
}

LinkedList /*<char*>*/
IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error,
        const char* logicalNodeReference, ACSIClass acsiClass)
{
    *error = IED_ERROR_OK;

    if (strlen(logicalNodeReference) > 129)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    char lnRefCopy[130];

    StringUtils_copyStringMax(lnRefCopy, 130, logicalNodeReference);

    char* ldSep = strchr(lnRefCopy, '/');

    if (ldSep == NULL)
    {
        *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
        return NULL;
    }

    *ldSep = 0;

    char* logicalDeviceName = lnRefCopy;

    char* logicalNodeName = ldSep + 1;

    if (acsiClass == ACSI_CLASS_LOG)
        return getLogicalNodeDirectoryLogs(self, error, logicalDeviceName, logicalNodeName);

    if (acsiClass == ACSI_CLASS_DATA_SET)
        return getLogicalNodeDirectoryDataSets(self, error, logicalDeviceName, logicalNodeName);

    if (self->logicalDevices == NULL)
        IedConnection_getDeviceModelFromServer(self, error);

    if (*error != IED_ERROR_OK)
        return NULL;

    /* search for logical device */

    LinkedList device = LinkedList_getNext(self->logicalDevices);

    ICLogicalDevice* ld = NULL;

    while (device)
    {
        ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data;

        if (strcmp(logicalDeviceName, ldCandidate->name) == 0)
        {
            ld = ldCandidate;
            break;
        }

        device = LinkedList_getNext(device);
    }

    if (ld == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    LinkedList lnDirectory = LinkedList_create();

    switch (acsiClass)
    {
    case ACSI_CLASS_DATA_OBJECT:
        {
            LinkedList variable = LinkedList_getNext(ld->variables);

            while (variable)
            {
                char* variableName = (char*) variable->data;

                char* fcPos = strchr(variableName, '$');

                if (fcPos)
                {
                    if (memcmp(fcPos + 1, "RP", 2) == 0)
                        goto next_element;

                    if (memcmp(fcPos + 1, "BR", 2) == 0)
                        goto next_element;

                    if (memcmp(fcPos + 1, "GO", 2) == 0)
                        goto next_element;

                    int lnNameLen = (int)(fcPos - variableName);

                    if (strncmp(variableName, logicalNodeName, lnNameLen) == 0)
                    {
                        char* fcEndPos = strchr(fcPos + 1, '$');

                        if (fcEndPos)
                        {
                            char* nameEndPos = strchr(fcEndPos + 1, '$');

                            if (nameEndPos == NULL)
                            {
                                char* dataObjectName = StringUtils_copyString(fcEndPos + 1);

                                if (!addToStringSet(lnDirectory, dataObjectName))
                                    GLOBAL_FREEMEM(dataObjectName);
                            }
                        }
                    }
                }

                next_element:

                variable = LinkedList_getNext(variable);
            }
        }
        break;

    case ACSI_CLASS_SGCB:
        {
            LinkedList variable = LinkedList_getNext(ld->variables);

            while (variable)
            {
                char* variableName = (char*) variable->data;

                if (strcmp(variableName, "LLN0$SP$SGCB") == 0)
                    LinkedList_add(lnDirectory, (void*) StringUtils_copyString("SGCB"));

                variable = LinkedList_getNext(variable);
            }
        }
        break;

    case ACSI_CLASS_BRCB:
        addVariablesWithFc("BR", logicalNodeName, ld->variables, lnDirectory);
        break;

    case ACSI_CLASS_URCB:
        addVariablesWithFc("RP", logicalNodeName, ld->variables, lnDirectory);
        break;

    case ACSI_CLASS_GoCB:
        addVariablesWithFc("GO", logicalNodeName, ld->variables, lnDirectory);
        break;

    case ACSI_CLASS_LCB:
        addVariablesWithFc("LG", logicalNodeName, ld->variables, lnDirectory);
        break;

    default:
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: ACSI class not yet supported!\n");
        break;
    }

    *error = IED_ERROR_OK;
    return lnDirectory;
}

LinkedList /*<char*>*/
IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error,
        const char* logicalNodeReference)
{
    *error = IED_ERROR_OK;

    if (strlen(logicalNodeReference) > 129)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    if (self->logicalDevices == NULL)
        IedConnection_getDeviceModelFromServer(self, error);

    if (*error != IED_ERROR_OK)
        return NULL;

    char lnRefCopy[130];

    StringUtils_copyStringMax(lnRefCopy, 130, logicalNodeReference);

    char* ldSep = strchr(lnRefCopy, '/');

    if (ldSep == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    *ldSep = 0;

    char* logicalDeviceName = lnRefCopy;

    char* logicalNodeName = ldSep + 1;

    /* search for logical device */

    LinkedList device = LinkedList_getNext(self->logicalDevices);

    ICLogicalDevice* ld = NULL;

    while (device)
    {
        ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data;

        if (strcmp(logicalDeviceName, ldCandidate->name) == 0)
        {
            ld = ldCandidate;
            break;
        }

        device = LinkedList_getNext(device);
    }

    if (ld == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    if (DEBUG_IED_CLIENT)
        printf("DEBUG_IED_CLIENT: Found LD %s search for variables of LN %s ...\n", logicalDeviceName, logicalNodeName);

    LinkedList variable = LinkedList_getNext(ld->variables);

    LinkedList lnDirectory = LinkedList_create();

    while (variable)
    {
        char* variableName = (char*) variable->data;

        char* fcPos = strchr(variableName, '$');

        if (fcPos)
        {
            int lnNameLen = (int)(fcPos - variableName);

            if (strncmp(variableName, logicalNodeName, lnNameLen) == 0)
            {
                LinkedList_add(lnDirectory, StringUtils_copyString(fcPos + 1));
            }
        }

        variable = LinkedList_getNext(variable);
    }

    *error = IED_ERROR_OK;
    return lnDirectory;
}

static LinkedList
getDataDirectory(IedConnection self, IedClientError* error,
        const char* dataReference, bool withFc)
{
    *error = IED_ERROR_OK;

    if (strlen(dataReference) > 129)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    if (self->logicalDevices == NULL)
        IedConnection_getDeviceModelFromServer(self, error);

    if (*error != IED_ERROR_OK)
        return NULL;

    char dataRefCopy[130];

    StringUtils_copyStringMax(dataRefCopy, 130, dataReference);

    char* ldSep = strchr(dataRefCopy, '/');

    *ldSep = 0;

    char* logicalDeviceName = dataRefCopy;

    char* logicalNodeName = ldSep + 1;

    char* logicalNodeNameEnd = strchr(logicalNodeName, '.');

    if (logicalNodeNameEnd == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    int logicalNodeNameLen = (int)(logicalNodeNameEnd - logicalNodeName);

    char* dataNamePart = logicalNodeNameEnd + 1;

    int dataNamePartLen = (int)strlen(dataNamePart);

    if (dataNamePartLen < 1)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    StringUtils_replace(dataNamePart, '.', '$');

    /* search for logical device */

    LinkedList device = LinkedList_getNext(self->logicalDevices);

    ICLogicalDevice* ld = NULL;

    while (device)
    {
        ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data;

        if (strcmp(logicalDeviceName, ldCandidate->name) == 0)
        {
            ld = ldCandidate;
            break;
        }

        device = LinkedList_getNext(device);
    }

    if (ld == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    LinkedList variable = LinkedList_getNext(ld->variables);

    LinkedList dataDirectory = LinkedList_create();

    while (variable)
    {
        char* variableName = (char*) variable->data;

        char* fcPos = strchr(variableName, '$');

        if (fcPos)
        {
            int lnNameLen = (int)(fcPos - variableName);

            if (logicalNodeNameLen == lnNameLen)
            {
                if (memcmp(variableName, logicalNodeName, lnNameLen) == 0)
                {
                    /* ok we are in the correct logical node */

                    /* skip FC */
                    char* fcEnd = strchr(fcPos + 1, '$');

                    if (fcEnd == NULL)
                        goto next_variable;

                    char* remainingPart = fcEnd + 1;

                    int remainingLen = (int)strlen(remainingPart);

                    if (remainingLen <= dataNamePartLen)
                        goto next_variable;

                    if (remainingPart[dataNamePartLen] == '$')
                    {
                        if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0)
                        {
                            char* subElementName = remainingPart + dataNamePartLen + 1;

                            char* subElementNameSep = strchr(subElementName, '$');

                            if (subElementNameSep)
                                goto next_variable;

                            char* elementName;

                            if (withFc)
                            {
                                int elementNameLen = (int)strlen(subElementName);

                                elementName = (char*) GLOBAL_MALLOC(elementNameLen + 5);

                                if (elementName)
                                {
                                    memcpy(elementName, subElementName, elementNameLen);
                                    elementName[elementNameLen] = '[';
                                    elementName[elementNameLen + 1] = *(fcPos + 1);
                                    elementName[elementNameLen + 2] = *(fcPos + 2);
                                    elementName[elementNameLen + 3] = ']';
                                    elementName[elementNameLen + 4] = 0;
                                }
                            }
                            else
                                elementName = StringUtils_copyString(subElementName);

                            if (!addToStringSet(dataDirectory, elementName))
                                GLOBAL_FREEMEM(elementName);
                        }
                    }
                }
            }
        }

        next_variable:

        variable = LinkedList_getNext(variable);
    }

    *error = IED_ERROR_OK;
    return dataDirectory;
}

LinkedList
IedConnection_getDataDirectory(IedConnection self, IedClientError* error, const char* dataReference)
{
    return getDataDirectory(self, error, dataReference, false);
}

LinkedList
IedConnection_getDataDirectoryFC(IedConnection self, IedClientError* error, const char* dataReference)
{
    return getDataDirectory(self, error, dataReference, true);
}

static LinkedList
getDataDirectoryByFc(IedConnection self, IedClientError* error,
        const char* dataReference, FunctionalConstraint fc)
{
    *error = IED_ERROR_OK;

    if (strlen(dataReference) > 129)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    char* fcString = FunctionalConstraint_toString(fc);

    if (fcString == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    if (self->logicalDevices == NULL)
        IedConnection_getDeviceModelFromServer(self, error);

    if (*error != IED_ERROR_OK)
        return NULL;

    char dataRefCopy[130];

    StringUtils_copyStringMax(dataRefCopy, 130, dataReference);

    char* ldSep = strchr(dataRefCopy, '/');

    *ldSep = 0;

    char* logicalDeviceName = dataRefCopy;

    char* logicalNodeName = ldSep + 1;

    char* logicalNodeNameEnd = strchr(logicalNodeName, '.');

    if (logicalNodeNameEnd == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    int logicalNodeNameLen = (int)(logicalNodeNameEnd - logicalNodeName);

    char* dataNamePart = logicalNodeNameEnd + 1;

    int dataNamePartLen = (int)strlen(dataNamePart);

    if (dataNamePartLen < 1)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    StringUtils_replace(dataNamePart, '.', '$');

    /* search for logical device */

    LinkedList device = LinkedList_getNext(self->logicalDevices);

    ICLogicalDevice* ld = NULL;

    while (device)
    {
        ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data;

        if (strcmp(logicalDeviceName, ldCandidate->name) == 0)
        {
            ld = ldCandidate;
            break;
        }

        device = LinkedList_getNext(device);
    }

    if (ld == NULL)
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }

    LinkedList variable = LinkedList_getNext(ld->variables);

    LinkedList dataDirectory = LinkedList_create();

    while (variable)
    {
        char* variableName = (char*) variable->data;

        char* fcPos = strchr(variableName, '$');

        if (fcPos)
        {
            int lnNameLen = (int)(fcPos - variableName);

            if (logicalNodeNameLen == lnNameLen)
            {
                if (memcmp(variableName, logicalNodeName, lnNameLen) == 0)
                {
                    /* ok we are in the correct logical node */

                    /* skip FC */
                    char* fcEnd = strchr(fcPos + 1, '$');

                    if (fcEnd == NULL)
                        goto next_variable;

                    if ((fcPos[1] != fcString[0]) || (fcPos[2] != fcString[1]))
                        goto next_variable;

                    char* remainingPart = fcEnd + 1;

                    int remainingLen = (int)strlen(remainingPart);

                    if (remainingLen <= dataNamePartLen)
                        goto next_variable;

                    if (remainingPart[dataNamePartLen] == '$')
                    {
                        if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0)
                        {
                            char* subElementName = remainingPart + dataNamePartLen + 1;

                            char* subElementNameSep = strchr(subElementName, '$');

                            if (subElementNameSep)
                                goto next_variable;

                            int elementNameLen = (int)strlen(subElementName);

                            char* elementName = (char*) GLOBAL_MALLOC(elementNameLen + 1);

                            if (elementName)
                            {
                                memcpy(elementName, subElementName, elementNameLen);
                                elementName[elementNameLen] = 0;
      
                                if (!addToStringSet(dataDirectory, elementName))
                                    GLOBAL_FREEMEM(elementName);
                            }
                        }
                    }
                }
            }
        }

        next_variable:

        variable = LinkedList_getNext(variable);
    }

    *error = IED_ERROR_OK;
    return dataDirectory;
}


LinkedList
IedConnection_getDataDirectoryByFC(IedConnection self, IedClientError* error, const char* dataReference, FunctionalConstraint fc)
{
    return getDataDirectoryByFc(self, error, dataReference, fc);
}

void
IedConnection_createDataSet(IedConnection self, IedClientError* error, const char* dataSetReference,
        LinkedList /* <char*> */dataSetElements)
{
    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId;
    const char* itemId;
    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            int domainIdLength = (int)strlen(domainId);

            if ((strlen(dataSetReference) - domainIdLength - 1) > 32)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(dataSetReference + domainIdLength + 1, itemIdBuffer);
            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    MmsError mmsError;

    LinkedList dataSetEntries = LinkedList_create();

    LinkedList dataSetElement = LinkedList_getNext(dataSetElements);

    while (dataSetElement)
    {
        MmsVariableAccessSpecification* dataSetEntry =
                MmsMapping_ObjectReferenceToVariableAccessSpec((char*) dataSetElement->data);

        if (dataSetEntry == NULL)
        {
            *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
            goto cleanup_list;
        }

        LinkedList_add(dataSetEntries, (void*) dataSetEntry);

        dataSetElement = LinkedList_getNext(dataSetElement);
    }

    if (isAssociationSpecific)
        MmsConnection_defineNamedVariableListAssociationSpecific(self->connection, &mmsError,
                itemId, dataSetEntries);
    else
        MmsConnection_defineNamedVariableList(self->connection, &mmsError,
                domainId, itemId, dataSetEntries);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

cleanup_list:
    /* delete list and all elements */
    LinkedList_destroyDeep(dataSetEntries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy);

exit_function:
    return;
}

bool
IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const char* dataSetReference)
{
    char domainIdBuf[65];
    char* domainId = domainIdBuf;
    char itemId[DATA_SET_MAX_NAME_LENGTH + 1];
    bool isAssociationSpecific = false;
    bool isDeleted = false;

    int dataSetReferenceLength = (int)strlen(dataSetReference);

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference + 1);
            else
                StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference);
        }
        else
        {
            if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainId) == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdString = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            StringUtils_copyStringToBuffer(itemIdString, itemId);

            StringUtils_replace(itemId, '.', '$');
        }
    }
    else
    {
        if (dataSetReferenceLength > 33)
        {
            *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
            goto exit_function;
        }

        StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference + 1);

        isAssociationSpecific = true;
    }

    MmsError mmsError;

    if (isAssociationSpecific)
        isDeleted = MmsConnection_deleteAssociationSpecificNamedVariableList(self->connection, &mmsError, itemId);
    else
        isDeleted = MmsConnection_deleteNamedVariableList(self->connection, &mmsError, domainId, itemId);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

exit_function:
    return isDeleted;
}

static void
deleteNamedVariableListHandler(uint32_t invokeId, void* parameter, MmsError mmsError, bool success)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler)call->callback;

        IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

        if (err == IED_ERROR_OK)
        {
            if (success == false)
                err = IED_ERROR_ACCESS_DENIED;
        }

        handler(invokeId, call->callbackParameter, err);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, const char* dataSetReference,
       IedConnection_GenericServiceHandler handler, void* parameter)
{
    *error = IED_ERROR_OK;

    char domainIdBuf[65];
    char *domainId = domainIdBuf;
    char itemId[DATA_SET_MAX_NAME_LENGTH + 1];
    bool isAssociationSpecific = false;

    int dataSetReferenceLength = (int)strlen(dataSetReference);

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference + 1);
            else
                StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference);
        }
        else
        {
            if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainId) == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            const char *itemIdString = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            StringUtils_copyStringToBuffer(itemIdString, itemId);

            StringUtils_replace(itemId, '.', '$');
        }
    }
    else
    {
        if (dataSetReferenceLength > 33)
        {
            *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
            return 0;
        }

        StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference + 1);

        isAssociationSpecific = true;
    }

    MmsError mmsError;

    if ((domainId == NULL) || (itemId[0] == 0))
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL) {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->invokeId = 0;

    if (isAssociationSpecific)
    {
        MmsConnection_deleteAssociationSpecificNamedVariableListAsync(self->connection, &(call->invokeId), &mmsError, itemId, deleteNamedVariableListHandler, self);
    }
    else
    {
        MmsConnection_deleteNamedVariableListAsync(self->connection, &(call->invokeId), &mmsError, domainId, itemId, deleteNamedVariableListHandler, self);
    }

    if (*error != IED_ERROR_OK)
    {
        iedConnection_releaseOutstandingCall(self, call);
        return 0;
    }

    return call->invokeId;
}

static void
createDataSetAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, bool success)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler)call->callback;

        IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError);

        if (err == IED_ERROR_OK)
        {
            if (success == false)
                err = IED_ERROR_ACCESS_DENIED;
        }

        handler(invokeId, call->callbackParameter, err);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList /* char* */ dataSetElements,
        IedConnection_GenericServiceHandler handler, void* parameter)
{
    MmsError mmsError = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        goto exit_function;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->invokeId = 0;

    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId;
    const char* itemId;
    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            int domainIdLength = (int)strlen(domainId);

            if ((strlen(dataSetReference) - domainIdLength - 1) > 32)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(dataSetReference + domainIdLength + 1, itemIdBuffer);
            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    LinkedList dataSetEntries = LinkedList_create();

    LinkedList dataSetElement = LinkedList_getNext(dataSetElements);

    while (dataSetElement)
    {
        MmsVariableAccessSpecification* dataSetEntry =
                MmsMapping_ObjectReferenceToVariableAccessSpec((char*) dataSetElement->data);

        if (dataSetEntry == NULL)
        {
            *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
            goto cleanup_list;
        }

        LinkedList_add(dataSetEntries, (void*) dataSetEntry);

        dataSetElement = LinkedList_getNext(dataSetElement);
    }

    if (isAssociationSpecific)
    {
        MmsConnection_defineNamedVariableListAssociationSpecificAsync(self->connection, &(call->invokeId),
                &mmsError, itemId, dataSetEntries, createDataSetAsyncHandler, self);
    }
    else
    {
        MmsConnection_defineNamedVariableListAsync(self->connection, &(call->invokeId),
                &mmsError, domainId, itemId, dataSetEntries, createDataSetAsyncHandler, self);
    }

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

cleanup_list:
    /* delete list and all elements */
    LinkedList_destroyDeep(dataSetEntries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy);

exit_function:

    if (*error != IED_ERROR_OK)
    {
        if (call)
            iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }
    else {
        return call->invokeId;
    }
}

LinkedList /* <char*> */
IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, const char* dataSetReference, bool* isDeletable)
{
    bool deletable = false;

    LinkedList dataSetMembers = NULL;

    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdRef = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRefInBuffer = StringUtils_copyStringToBuffer(itemIdRef, itemIdBuffer);
            StringUtils_replace(itemIdRefInBuffer, '.', '$');
            itemId = itemIdRefInBuffer;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    MmsError mmsError;

    LinkedList entries;

    if (isAssociationSpecific)
        entries = MmsConnection_readNamedVariableListDirectoryAssociationSpecific(self->connection,
                &mmsError, itemId, &deletable);
    else
        entries = MmsConnection_readNamedVariableListDirectory(self->connection,
                &mmsError, domainId, itemId, &deletable);

    if (mmsError == MMS_ERROR_NONE)
    {
        LinkedList entry = LinkedList_getNext(entries);

        dataSetMembers = LinkedList_create();

        while (entry)
        {
            MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*)LinkedList_getData(entry);

            char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec);

            LinkedList_add(dataSetMembers, objectReference);

            entry = LinkedList_getNext(entry);
        }

        if (isDeletable)
            *isDeletable = deletable;

        LinkedList_destroyDeep(entries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy);
    }

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

exit_function:
    return dataSetMembers;
}

static void
getDataSetDirectoryAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, LinkedList /* <MmsVariableAccessSpecification*> */ specs, bool deletable)
{
    IedConnection self = (IedConnection)parameter;

    IedClientError err = IED_ERROR_OK;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        LinkedList dataSetMembers = NULL;

        if (mmsError != MMS_ERROR_NONE)
            err = iedConnection_mapMmsErrorToIedError(mmsError);

        if (specs)
        {
            dataSetMembers = LinkedList_create();
            LinkedList specElem = LinkedList_getNext(specs);

            while (specElem)
            {
                MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*)LinkedList_getData(specElem);

                char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec);

                LinkedList_add(dataSetMembers, objectReference);

                specElem = LinkedList_getNext(specElem);
            }
        }

        IedConnection_GetDataSetDirectoryHandler handler = (IedConnection_GetDataSetDirectoryHandler)call->callback;

        if (handler)
            handler(call->invokeId, call->callbackParameter, err, dataSetMembers, deletable);

        iedConnection_releaseOutstandingCall(self, call);
    }

    if (specs)
        LinkedList_destroyDeep(specs, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy);
}

uint32_t
IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error, const char* dataSetReference,
        IedConnection_GetDataSetDirectoryHandler handler, void* parameter)
{
    MmsError mmsError = MMS_ERROR_NONE;

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->invokeId = 0;

    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdRef = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRefInBuffer = StringUtils_copyStringToBuffer(itemIdRef, itemIdBuffer);
            StringUtils_replace(itemIdRefInBuffer, '.', '$');
            itemId = itemIdRefInBuffer;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    if (isAssociationSpecific)
        MmsConnection_readNamedVariableListDirectoryAssociationSpecificAsync(self->connection, &(call->invokeId), &mmsError, itemId, getDataSetDirectoryAsyncHandler, self);

    else
        MmsConnection_readNamedVariableListDirectoryAsync(self->connection, &(call->invokeId), &mmsError, domainId, itemId, getDataSetDirectoryAsyncHandler, self);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

exit_function:

    if (*error != IED_ERROR_OK)
    {
        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }
    else
    {
        return call->invokeId;
    }
}

ClientDataSet
IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference,
        ClientDataSet dataSet)
{
    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(itemIdRefOrig, itemIdBuffer);

            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    MmsError mmsError;

    MmsValue* dataSetVal;

    if (isAssociationSpecific)
        dataSetVal = MmsConnection_readNamedVariableListValuesAssociationSpecific(self->connection,
                &mmsError, itemId, true);
    else
        dataSetVal = MmsConnection_readNamedVariableListValues(self->connection, &mmsError,
                    domainId, itemId, true);

    if (dataSetVal == NULL)
    {
        *error = iedConnection_mapMmsErrorToIedError(mmsError);
        goto exit_function;
    }
    else
        *error = IED_ERROR_OK;

    if (dataSet == NULL)
    {
        dataSet = ClientDataSet_create(dataSetReference);
        ClientDataSet_setDataSetValues(dataSet, dataSetVal);
    }
    else
    {
        MmsValue* dataSetValues = ClientDataSet_getValues(dataSet);
        MmsValue_update(dataSetValues, dataSetVal);
        MmsValue_delete(dataSetVal);
    }

exit_function:
    return dataSet;
}

static void
getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsValue* value)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_ReadDataSetHandler handler =  (IedConnection_ReadDataSetHandler) call->callback;

        ClientDataSet dataSet = (ClientDataSet) call->specificParameter;
        char* dataSetReference = (char*) call->specificParameter2.pointer;

        if (value)
        {
            if (dataSet == NULL)
            {
                dataSet = ClientDataSet_create(dataSetReference);
                ClientDataSet_setDataSetValues(dataSet, MmsValue_clone(value));
            }
            else
            {
                MmsValue* dataSetValues = ClientDataSet_getValues(dataSet);
                MmsValue_update(dataSetValues, value);
            }

            MmsValue_delete(value);
        }

        if (dataSetReference)
            GLOBAL_FREEMEM(dataSetReference);

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), dataSet);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference, ClientDataSet dataSet,
        IedConnection_ReadDataSetHandler handler, void* parameter)
{
    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(itemIdRefOrig, itemIdBuffer);

            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;
    call->specificParameter = dataSet;

    if (dataSet == NULL)
        call->specificParameter2.pointer = StringUtils_copyString(dataSetReference);
    else
        call->specificParameter2.pointer = NULL;

    MmsError err = MMS_ERROR_NONE;

    if (isAssociationSpecific)
        MmsConnection_readNamedVariableListValuesAssociationSpecificAsync(self->connection, &(call->invokeId),
                &err, itemId, true, getDataSetHandlerInternal, self);
    else
        MmsConnection_readNamedVariableListValuesAsync(self->connection, &(call->invokeId), &err,
                    domainId, itemId, true, getDataSetHandlerInternal, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        GLOBAL_FREEMEM(call->specificParameter2.pointer);

        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }

    return call->invokeId;
}

void
IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference,
        LinkedList/*<MmsValue*>*/ values, /* OUTPUT */LinkedList* /* <MmsValue*> */accessResults)
{
    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(itemIdRefOrig, itemIdBuffer);

            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    MmsError mmsError;

    MmsConnection_writeNamedVariableList(self->connection, &mmsError, isAssociationSpecific, domainId, itemId, values, accessResults);

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

exit_function:
    return;
}

static void
writeDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, LinkedList /* <MmsValue*> */ accessResults)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_WriteDataSetHandler handler =  (IedConnection_WriteDataSetHandler) call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), accessResults);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

LIB61850_API uint32_t
IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference,
        LinkedList/*<MmsValue*>*/ values, IedConnection_WriteDataSetHandler handler, void* parameter)
{
    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@')
    {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL))
        {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else
        {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH)
            {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                return 0;
            }

            char* itemIdRef = StringUtils_copyStringToBuffer(itemIdRefOrig, itemIdBuffer);

            StringUtils_replace(itemIdRef, '.', '$');
            itemId = itemIdRef;
        }
    }
    else
    {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

    if (call == NULL)
    {
        *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
        return 0;
    }

    call->callback = handler;
    call->callbackParameter = parameter;

    MmsError err = MMS_ERROR_NONE;

    MmsConnection_writeNamedVariableListAsync(self->connection, &(call->invokeId), &err, isAssociationSpecific, domainId, itemId, values, writeDataSetHandlerInternal, self);

    *error = iedConnection_mapMmsErrorToIedError(err);

    if (err != MMS_ERROR_NONE)
    {
        iedConnection_releaseOutstandingCall(self, call);

        return 0;
    }

    return call->invokeId;
}

LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference,
        uint64_t startTime, uint64_t endTime, bool* moreFollows)
{
    MmsError mmsError;

    char logRef[130];

    StringUtils_copyStringMax(logRef, 130, logReference);

    char* logDomain = logRef;
    char* logName = strchr(logRef, '/');

    if (logName)
    {
        logName[0] = 0;
        logName++;

        MmsValue* startTimeMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(startTimeMms, startTime);

        MmsValue* endTimeMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(endTimeMms, endTime);

        LinkedList journalEntries = MmsConnection_readJournalTimeRange(self->connection, &mmsError, logDomain, logName,
                startTimeMms, endTimeMms, moreFollows);

        MmsValue_delete(startTimeMms);
        MmsValue_delete(endTimeMms);

        if (mmsError != MMS_ERROR_NONE)
        {
            *error = iedConnection_mapMmsErrorToIedError(mmsError);
            return NULL;
        }
        else
            return journalEntries;
    }
    else
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }
}

static void
readJournalHandler(uint32_t invokeId, void* parameter, MmsError err, LinkedList /* <MmsJournalEntry> */ journalEntries, bool moreFollows)
{
    IedConnection self = (IedConnection) parameter;

    IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);

    if (call)
    {
        IedConnection_QueryLogHandler handler =  (IedConnection_QueryLogHandler) call->callback;

        handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), journalEntries, moreFollows);

        iedConnection_releaseOutstandingCall(self, call);
    }
    else
    {
        if (DEBUG_IED_CLIENT)
            printf("IED_CLIENT: internal error - no matching outstanding call!\n");
    }
}

uint32_t
IedConnection_queryLogByTimeAsync(IedConnection self, IedClientError* error, const char* logReference,
        uint64_t startTime, uint64_t endTime, IedConnection_QueryLogHandler handler, void* parameter)
{
    char logRef[130];

    StringUtils_copyStringMax(logRef, 130, logReference);

    char* logDomain = logRef;
    char* logName = strchr(logRef, '/');

    if (logName)
    {
        logName[0] = 0;
        logName++;

        IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

        if (call == NULL)
        {
            *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
            return 0;
        }

        call->callback = handler;
        call->callbackParameter = parameter;

        MmsError err = MMS_ERROR_NONE;

        MmsValue* startTimeMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(startTimeMms, startTime);

        MmsValue* endTimeMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(endTimeMms, endTime);

        MmsConnection_readJournalTimeRangeAsync(self->connection, &(call->invokeId), &err, logDomain, logName,
                startTimeMms, endTimeMms, readJournalHandler, self);

        MmsValue_delete(startTimeMms);
        MmsValue_delete(endTimeMms);

        *error = iedConnection_mapMmsErrorToIedError(err);

        if (err != MMS_ERROR_NONE)
        {
            iedConnection_releaseOutstandingCall(self, call);
            return 0;
        }

        return call->invokeId;
    }
    else
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }
}

uint32_t
IedConnection_queryLogAfterAsync(IedConnection self, IedClientError* error, const char* logReference,
        MmsValue* entryID, uint64_t timeStamp, IedConnection_QueryLogHandler handler, void* parameter)
{
    char logRef[130];

    StringUtils_copyStringMax(logRef, 130, logReference);

    char* logDomain = logRef;
    char* logName = strchr(logRef, '/');

    if (logName)
    {
        logName[0] = 0;
        logName++;

        IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);

        if (call == NULL)
        {
            *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
            return 0;
        }

        call->callback = handler;
        call->callbackParameter = parameter;

        MmsError err = MMS_ERROR_NONE;

        MmsValue* timeStampMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(timeStampMms, timeStamp);

        MmsConnection_readJournalStartAfterAsync(self->connection, &(call->invokeId), &err, logDomain, logName,
               timeStampMms, entryID, readJournalHandler, self);

        MmsValue_delete(timeStampMms);

        *error = iedConnection_mapMmsErrorToIedError(err);

        if (err != MMS_ERROR_NONE)
        {
            iedConnection_releaseOutstandingCall(self, call);
            return 0;
        }

        return call->invokeId;
    }
    else
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return 0;
    }
}

LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference,
        MmsValue* entryID, uint64_t timeStamp, bool* moreFollows)
{
    MmsError mmsError;

    char logRef[130];

    StringUtils_copyStringMax(logRef, 130, logReference);

    char* logDomain = logRef;
    char* logName = strchr(logRef, '/');

    if (logName)
    {
        logName[0] = 0;
        logName++;

        MmsValue* timeStampMms = MmsValue_newBinaryTime(false);
        MmsValue_setBinaryTime(timeStampMms, timeStamp);

        LinkedList journalEntries = MmsConnection_readJournalStartAfter(self->connection, &mmsError, logDomain, logName,
               timeStampMms, entryID,  moreFollows);

        MmsValue_delete(timeStampMms);

        if (mmsError != MMS_ERROR_NONE)
        {
            *error = iedConnection_mapMmsErrorToIedError(mmsError);
            return NULL;
        }
        else
            return journalEntries;
    }
    else
    {
        *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
        return NULL;
    }
}

MmsConnection
IedConnection_getMmsConnection(IedConnection self)
{
    return self->connection;
}

LastApplError
IedConnection_getLastApplError(IedConnection self)
{
    return self->lastApplError;
}

void
iedConnection_addControlClient(IedConnection self, ControlObjectClient control)
{
    Semaphore_wait(self->clientControlsLock);

    LinkedList_add(self->clientControls, control);

    Semaphore_post(self->clientControlsLock);
}

void
iedConnection_removeControlClient(IedConnection self, ControlObjectClient control)
{
    Semaphore_wait(self->clientControlsLock);

    LinkedList_remove(self->clientControls, control);

    Semaphore_post(self->clientControlsLock);
}

FileDirectoryEntry
FileDirectoryEntry_create(const char* fileName, uint32_t fileSize, uint64_t lastModified)
{
    FileDirectoryEntry self = (FileDirectoryEntry) GLOBAL_CALLOC(1, sizeof(struct sFileDirectoryEntry));

    if (self)
    {
        self->fileName = StringUtils_copyString(fileName);
        self->fileSize = fileSize;
        self->lastModified = lastModified;
    }

    return self;
}

void
FileDirectoryEntry_destroy(FileDirectoryEntry self)
{
    if (self)
    {
        GLOBAL_FREEMEM(self->fileName);
        GLOBAL_FREEMEM(self);
    }
}

const char*
FileDirectoryEntry_getFileName(FileDirectoryEntry self)
{
    return self->fileName;
}

uint32_t
FileDirectoryEntry_getFileSize(FileDirectoryEntry self)
{
    return self->fileSize;
}

uint64_t
FileDirectoryEntry_getLastModified(FileDirectoryEntry self)
{
    return self->lastModified;
}
