/*
* client_report_control.c
*
* Implementation of the ClientReportControlBlock class
*
* Copyright 2014-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 .
*
* See COPYING file for the complete license text.
*/
#include "iec61850_client.h"
#include "stack_config.h"
#include "ied_connection_private.h"
#include "libiec61850_platform_includes.h"
struct sClientGooseControlBlock
{
char* objectReference;
MmsValue* goEna;
MmsValue* goID;
MmsValue* datSet;
MmsValue* confRev;
MmsValue* ndsCom;
MmsValue* dstAddress;
MmsValue* minTime;
MmsValue* maxTime;
MmsValue* fixedOffs;
};
ClientGooseControlBlock
ClientGooseControlBlock_create(const char* objectReference)
{
ClientGooseControlBlock self = (ClientGooseControlBlock) GLOBAL_CALLOC(1, sizeof(struct sClientGooseControlBlock));
if (self)
{
self->objectReference = StringUtils_copyString(objectReference);
}
return self;
}
void
ClientGooseControlBlock_destroy(ClientGooseControlBlock self)
{
if (self)
{
GLOBAL_FREEMEM(self->objectReference);
MmsValue_delete(self->goEna);
MmsValue_delete(self->goID);
MmsValue_delete(self->datSet);
MmsValue_delete(self->confRev);
MmsValue_delete(self->ndsCom);
MmsValue_delete(self->dstAddress);
MmsValue_delete(self->minTime);
MmsValue_delete(self->maxTime);
MmsValue_delete(self->fixedOffs);
GLOBAL_FREEMEM(self);
}
}
bool
ClientGooseControlBlock_getGoEna(ClientGooseControlBlock self)
{
if (self->goEna != NULL)
return MmsValue_getBoolean(self->goEna);
else
return false;
}
void
ClientGooseControlBlock_setGoEna(ClientGooseControlBlock self, bool goEna)
{
if (self->goEna == NULL)
self->goEna = MmsValue_newBoolean(goEna);
else
MmsValue_setBoolean(self->goEna, goEna);
}
const char*
ClientGooseControlBlock_getGoID(ClientGooseControlBlock self)
{
if (self->goID != NULL)
return MmsValue_toString(self->goID);
else
return NULL;
}
void
ClientGooseControlBlock_setGoID(ClientGooseControlBlock self, const char* goID)
{
if (self->goID == NULL)
self->goID = MmsValue_newVisibleString(goID);
else
MmsValue_setVisibleString(self->goID, goID);
}
const char*
ClientGooseControlBlock_getDatSet(ClientGooseControlBlock self)
{
if (self->datSet != NULL)
return MmsValue_toString(self->datSet);
else
return NULL;
}
void
ClientGooseControlBlock_setDatSet(ClientGooseControlBlock self, const char* datSet)
{
if (self->datSet == NULL)
self->datSet = MmsValue_newVisibleString(datSet);
else
MmsValue_setVisibleString(self->datSet, datSet);
}
uint32_t
ClientGooseControlBlock_getConfRev(ClientGooseControlBlock self)
{
if (self->confRev != NULL)
return MmsValue_toUint32(self->confRev);
else
return 0;
}
bool
ClientGooseControlBlock_getNdsComm(ClientGooseControlBlock self)
{
if (self->ndsCom != NULL)
return MmsValue_getBoolean(self->ndsCom);
else
return false;
}
uint32_t
ClientGooseControlBlock_getMinTime(ClientGooseControlBlock self)
{
if (self->minTime != NULL)
return MmsValue_toUint32(self->minTime);
else
return 0;
}
uint32_t
ClientGooseControlBlock_getMaxTime(ClientGooseControlBlock self)
{
if (self->maxTime != NULL)
return MmsValue_toUint32(self->maxTime);
else
return 0;
}
bool
ClientGooseControlBlock_getFixedOffs(ClientGooseControlBlock self)
{
if (self->fixedOffs != NULL)
return MmsValue_getBoolean(self->fixedOffs);
else
return false;
}
static MmsValue*
newEmptyPhyCommAddress(void)
{
MmsValue* self = MmsValue_createEmptyStructure(4);
if (self)
{
MmsValue_setElement(self, 0, MmsValue_newOctetString(6, 6));
MmsValue_setElement(self, 1, MmsValue_newUnsigned(8));
MmsValue_setElement(self, 2, MmsValue_newUnsigned(16));
MmsValue_setElement(self, 3, MmsValue_newUnsigned(16));
}
return self;
}
PhyComAddress
ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self)
{
PhyComAddress retVal;
memset(&retVal, 0, sizeof(retVal));
if (self->dstAddress == NULL) goto exit_error;
if (MmsValue_getType(self->dstAddress) != MMS_STRUCTURE)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n");
goto exit_error;
}
if (MmsValue_getArraySize(self->dstAddress) != 4)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n");
goto exit_error;
}
MmsValue* addr = MmsValue_getElement(self->dstAddress, 0);
if (MmsValue_getType(addr) != MMS_OCTET_STRING)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n");
goto exit_error;
}
if (MmsValue_getOctetStringSize(addr) != 6)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong size\n");
goto exit_error;
}
uint8_t* addrBuf = MmsValue_getOctetStringBuffer(addr);
memcpy(&(retVal.dstAddress), addrBuf, 6);
MmsValue* prio = MmsValue_getElement(self->dstAddress, 1);
if (MmsValue_getType(prio) != MMS_UNSIGNED)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - prio has wrong type\n");
goto exit_error;
}
retVal.vlanPriority = MmsValue_toUint32(prio);
MmsValue* vid = MmsValue_getElement(self->dstAddress, 2);
if (MmsValue_getType(vid) != MMS_UNSIGNED)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - vid has wrong type\n");
goto exit_error;
}
retVal.vlanId = MmsValue_toUint32(vid);
MmsValue* appID = MmsValue_getElement(self->dstAddress, 3);
if (MmsValue_getType(appID) != MMS_UNSIGNED)
{
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - appID has wrong type\n");
goto exit_error;
}
retVal.appId = MmsValue_toUint32(appID);
exit_error:
return retVal;
}
void
ClientGooseControlBlock_setDstAddress(ClientGooseControlBlock self, PhyComAddress value)
{
if (self->dstAddress == NULL)
self->dstAddress = newEmptyPhyCommAddress();
if (self->dstAddress)
{
MmsValue* addr = MmsValue_getElement(self->dstAddress, 0);
MmsValue_setOctetString(addr, value.dstAddress, 6);
MmsValue* prio = MmsValue_getElement(self->dstAddress, 1);
MmsValue_setUint8(prio, value.vlanPriority);
MmsValue* vid = MmsValue_getElement(self->dstAddress, 2);
MmsValue_setUint16(vid, value.vlanId);
MmsValue* appID = MmsValue_getElement(self->dstAddress, 3);
MmsValue_setUint16(appID, value.appId);
}
}
MmsValue*
ClientGooseControlBlock_getDstAddress_addr(ClientGooseControlBlock self)
{
if (self->dstAddress != NULL)
return MmsValue_getElement(self->dstAddress, 0);
else
return NULL;
}
void
ClientGooseControlBlock_setDstAddress_addr(ClientGooseControlBlock self, MmsValue* macAddr)
{
if (self->dstAddress == NULL)
self->dstAddress = newEmptyPhyCommAddress();
MmsValue* addr = MmsValue_getElement(self->dstAddress, 0);
MmsValue_update(addr, macAddr);
}
uint8_t
ClientGooseControlBlock_getDstAddress_priority(ClientGooseControlBlock self)
{
if (self->dstAddress != NULL)
return (uint8_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 1));
else
return 0;
}
void
ClientGooseControlBlock_setDstAddress_priority(ClientGooseControlBlock self, uint8_t priorityValue)
{
if (self->dstAddress == NULL)
self->dstAddress = newEmptyPhyCommAddress();
MmsValue* priority = MmsValue_getElement(self->dstAddress, 1);
MmsValue_setUint8(priority, priorityValue);
}
uint16_t
ClientGooseControlBlock_getDstAddress_vid(ClientGooseControlBlock self)
{
if (self->dstAddress != NULL)
return (uint16_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 2));
else
return 0;
}
void
ClientGooseControlBlock_setDstAddress_vid(ClientGooseControlBlock self, uint16_t vidValue)
{
if (self->dstAddress == NULL)
self->dstAddress = newEmptyPhyCommAddress();
MmsValue* vid = MmsValue_getElement(self->dstAddress, 2);
MmsValue_setUint16(vid, vidValue);
}
uint16_t
ClientGooseControlBlock_getDstAddress_appid(ClientGooseControlBlock self)
{
if (self->dstAddress != NULL)
return (uint16_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 3));
else
return 0;
}
void
ClientGooseControlBlock_setDstAddress_appid(ClientGooseControlBlock self, uint16_t appidValue)
{
if (self->dstAddress == NULL)
self->dstAddress = newEmptyPhyCommAddress();
MmsValue* appid = MmsValue_getElement(self->dstAddress, 3);
MmsValue_setUint16(appid, appidValue);
}
static void
updateOrClone(MmsValue** valuePtr, MmsValue* values, int index)
{
if (*valuePtr != NULL)
MmsValue_update(*valuePtr, MmsValue_getElement(values, index));
else
*valuePtr = MmsValue_clone(MmsValue_getElement(values, index));
}
static bool
private_ClientGooseControlBlock_updateValues(ClientGooseControlBlock self, MmsValue* values)
{
int elementCount = MmsValue_getArraySize(values);
if (elementCount > 5)
{
updateOrClone(&(self->goEna), values, 0);
updateOrClone(&(self->goID), values, 1);
updateOrClone(&(self->datSet), values, 2);
updateOrClone(&(self->confRev), values, 3);
updateOrClone(&(self->ndsCom), values, 4);
updateOrClone(&(self->dstAddress), values, 5);
}
else
return false;
if (elementCount > 6)
updateOrClone(&(self->minTime), values, 6);
if (elementCount > 7)
updateOrClone(&(self->maxTime), values, 7);
if (elementCount > 8)
updateOrClone(&(self->fixedOffs), values, 8);
return true;
}
ClientGooseControlBlock
IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB)
{
MmsError mmsError = MMS_ERROR_NONE;
*error = IED_ERROR_OK;
ClientGooseControlBlock returnGoCB = updateGoCB;
char domainId[65];
char itemId[130];
if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return NULL;
}
int domainIdSize = strlen(domainId);
const char* itemIdStart = goCBReference + domainIdSize + 1;
const char* separator = strchr(itemIdStart, '.');
if (separator == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return NULL;
}
int separatorOffset = separator - itemIdStart;
memcpy(itemId, itemIdStart, separatorOffset);
itemId[separatorOffset] = '$';
itemId[separatorOffset + 1] = 'G';
itemId[separatorOffset + 2] = 'O';
itemId[separatorOffset + 3] = '$';
itemId[separatorOffset + 4] = 0;
StringUtils_appendString(itemId, 130, separator + 1);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getGoCBValues for %s\n", goCBReference);
MmsValue* goCB = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId);
if (mmsError != MMS_ERROR_NONE)
{
*error = iedConnection_mapMmsErrorToIedError(mmsError);
return NULL;
}
if (goCB == NULL)
{
*error = IED_ERROR_OBJECT_DOES_NOT_EXIST;
return NULL;
}
if (MmsValue_getType(goCB) != MMS_STRUCTURE)
{
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n");
MmsValue_delete(goCB);
*error = IED_ERROR_UNKNOWN;
return NULL;
}
if (returnGoCB == NULL)
returnGoCB = ClientGooseControlBlock_create(goCBReference);
if (private_ClientGooseControlBlock_updateValues(returnGoCB, goCB))
*error = IED_ERROR_OK;
else
*error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED;
MmsValue_delete(goCB);
return returnGoCB;
}
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_GetGoCBValuesHandler handler = (IedConnection_GetGoCBValuesHandler) call->callback;
ClientGooseControlBlock updateGoCB = (ClientGooseControlBlock) call->specificParameter;
char* goCBReference = (char*) call->specificParameter2.pointer;
if (err != MMS_ERROR_NONE)
{
handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), NULL);
}
else
{
if (value == NULL)
{
handler(invokeId, call->callbackParameter, IED_ERROR_OBJECT_DOES_NOT_EXIST, NULL);
}
else
{
if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
{
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getGoCBValues returned data-access-error!\n");
handler(invokeId, call->callbackParameter, iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)), NULL);
}
else
{
ClientGooseControlBlock returnGoCB = updateGoCB;
if (returnGoCB == NULL)
returnGoCB = ClientGooseControlBlock_create(goCBReference);
if (private_ClientGooseControlBlock_updateValues(returnGoCB, value))
{
handler(invokeId, call->callbackParameter, IED_ERROR_OK, returnGoCB);
}
else
{
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getGoCBValues returned wrong type!\n");
handler(invokeId, call->callbackParameter, IED_ERROR_TYPE_INCONSISTENT, NULL);
if (updateGoCB == NULL)
ClientGooseControlBlock_destroy(returnGoCB);
}
}
MmsValue_delete(value);
}
}
GLOBAL_FREEMEM(goCBReference);
iedConnection_releaseOutstandingCall(self, call);
}
else
{
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
}
uint32_t
IedConnection_getGoCBValuesAsync(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB,
IedConnection_GetGoCBValuesHandler handler, void* parameter)
{
*error = IED_ERROR_OK;
char domainId[65];
char itemId[130];
if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return 0;
}
int domainIdSize = strlen(domainId);
const char* itemIdStart = goCBReference + domainIdSize + 1;
const char* separator = strchr(itemIdStart, '.');
if (separator == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return 0;
}
int separatorOffset = separator - itemIdStart;
memcpy(itemId, itemIdStart, separatorOffset);
itemId[separatorOffset] = '$';
itemId[separatorOffset + 1] = 'G';
itemId[separatorOffset + 2] = 'O';
itemId[separatorOffset + 3] = '$';
itemId[separatorOffset + 4] = 0;
StringUtils_appendString(itemId, 130, separator + 1);
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 = updateGoCB;
call->specificParameter2.pointer = StringUtils_copyString(goCBReference);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: readGoCBValues for %s\n", goCBReference);
MmsError err = MMS_ERROR_NONE;
MmsConnection_readVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, readObjectHandlerInternal, 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_setGoCBValues(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB,
uint32_t parametersMask, bool singleRequest)
{
*error = IED_ERROR_OK;
MmsError mmsError = MMS_ERROR_NONE;
char domainId[65];
char itemId[130];
if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return;
}
char* itemIdStart = goCB->objectReference + strlen(domainId) + 1;
char* separator = strchr(itemIdStart, '.');
if (separator == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return;
}
int separatorOffset = separator - itemIdStart;
memcpy(itemId, itemIdStart, separatorOffset);
itemId[separatorOffset] = '$';
itemId[separatorOffset + 1] = 'G';
itemId[separatorOffset + 2] = 'O';
itemId[separatorOffset + 3] = '$';
itemId[separatorOffset + 4] = 0;
StringUtils_appendString(itemId, 130, separator + 1);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: setGoCBValues for %s\n", goCB->objectReference);
int itemIdLen = strlen(itemId);
/* create the list of requested itemIds references */
LinkedList itemIds = LinkedList_create();
LinkedList values = LinkedList_create();
/* add rGoEna as last element */
if (parametersMask & GOCB_ELEMENT_GO_ID)
{
StringUtils_appendString(itemId, 130, "$GoID");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->goID);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_DATSET)
{
StringUtils_appendString(itemId, 130, "$DatSet");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->datSet);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_CONF_REV)
{
StringUtils_appendString(itemId, 130, "$ConfRev");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->confRev);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_NDS_COMM)
{
StringUtils_appendString(itemId, 130, "$NdsCom");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->ndsCom);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_DST_ADDRESS)
{
StringUtils_appendString(itemId, 130, "$DstAddress");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->dstAddress);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_MIN_TIME)
{
StringUtils_appendString(itemId, 130, "$MinTime");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->minTime);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_MAX_TIME)
{
StringUtils_appendString(itemId, 130, "$MaxTime");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->maxTime);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_FIXED_OFFS)
{
StringUtils_appendString(itemId, 130, "$FixedOffs");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->fixedOffs);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_GO_ENA)
{
StringUtils_appendString(itemId, 130, "$GoEna");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->goEna);
itemId[itemIdLen] = 0;
}
if (singleRequest)
{
LinkedList accessResults = NULL;
*error = IED_ERROR_OK;
MmsConnection_writeMultipleVariables(self->connection, &mmsError, domainId, itemIds, values, &accessResults);
if (accessResults)
{
LinkedList element = LinkedList_getNext(accessResults);
while (element)
{
MmsValue* accessResult = (MmsValue*) element->data;
MmsDataAccessError resErr = MmsValue_getDataAccessError(accessResult);
if (MmsValue_getDataAccessError(accessResult) != DATA_ACCESS_ERROR_SUCCESS)
{
*error = iedConnection_mapDataAccessErrorToIedError(resErr);
break;
}
element = LinkedList_getNext(element);
}
LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete);
}
goto exit_function;
}
else
{
LinkedList itemIdElement = LinkedList_getNext(itemIds);
LinkedList valueElement = LinkedList_getNext(values);
while (itemIdElement)
{
char* rcbItemId = (char*) itemIdElement->data;
MmsValue* value = (MmsValue*) valueElement->data;
MmsConnection_writeVariable(self->connection, &mmsError, domainId, rcbItemId, value);
if (mmsError != MMS_ERROR_NONE)
break;
itemIdElement = LinkedList_getNext(itemIdElement);
valueElement = LinkedList_getNext(valueElement);
}
*error = iedConnection_mapMmsErrorToIedError(mmsError);
goto exit_function;
}
exit_function:
LinkedList_destroy(itemIds);
LinkedList_destroyStatic(values);
}
static void
writeMultipleVariablesHandler(uint32_t invokeId, void* parameter, MmsError mmsError, LinkedList /* */ accessResults)
{
IedConnection self = (IedConnection)parameter;
IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);
if (call)
{
IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback;
if (accessResults)
{
IedClientError error = IED_ERROR_OK;
LinkedList accessResult = LinkedList_getNext(accessResults);
while (accessResult)
{
MmsValue* dataAccessError = (MmsValue*) accessResult->data;
if (MmsValue_getDataAccessError(dataAccessError) != DATA_ACCESS_ERROR_SUCCESS)
{
error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(dataAccessError));
break;
}
accessResult = LinkedList_getNext(accessResult);
}
LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction)MmsValue_delete);
handler(invokeId, call->callbackParameter, error);
}
else
{
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 with invoke ID: %u!\n", invokeId);
}
}
struct sWriteGoCBVariablesParameter
{
LinkedList itemIds;
LinkedList values;
LinkedList currentItemId;
LinkedList currentValue;
char* domainId;
uint32_t originalInvokeId;
};
static void
releaseWriteCall(IedConnection self, IedConnectionOutstandingCall call, struct sWriteGoCBVariablesParameter* param)
{
GLOBAL_FREEMEM(param->domainId);
LinkedList_destroy(param->itemIds);
LinkedList_destroyStatic(param->values);
GLOBAL_FREEMEM(param);
iedConnection_releaseOutstandingCall(self, call);
}
static void
writeVariableHandler(uint32_t invokeId, void* parameter, MmsError mmsError, MmsDataAccessError accessError)
{
IedConnection self = (IedConnection) parameter;
IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);
if (call)
{
IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback;
struct sWriteGoCBVariablesParameter* param = (struct sWriteGoCBVariablesParameter*) call->specificParameter2.pointer;
if ((mmsError != MMS_ERROR_NONE) || (accessError != DATA_ACCESS_ERROR_SUCCESS))
{
IedClientError err;
if (mmsError != MMS_ERROR_NONE)
err = iedConnection_mapMmsErrorToIedError(mmsError);
else
err = iedConnection_mapDataAccessErrorToIedError(accessError);
handler(param->originalInvokeId, call->callbackParameter, err);
releaseWriteCall(self, call, param);
}
param->currentItemId = LinkedList_getNext(param->currentItemId);
if (param->currentItemId == NULL)
{
handler(param->originalInvokeId, call->callbackParameter, IED_ERROR_OK);
releaseWriteCall(self, call, param);
}
else
{
param->currentValue = LinkedList_getNext(param->currentValue);
char* itemId = (char*) LinkedList_getData(param->currentItemId);
MmsValue* value = (MmsValue*)LinkedList_getData(param->currentValue);
MmsError writeError;
MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &writeError, param->domainId, itemId, value, writeVariableHandler, self);
if (writeError != MMS_ERROR_NONE)
{
handler(param->originalInvokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(writeError));
releaseWriteCall(self, call, param);
}
}
}
else
{
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
}
uint32_t
IedConnection_setGoCBValuesAsync(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB,
uint32_t parametersMask, bool singleRequest, IedConnection_GenericServiceHandler handler, void* parameter)
{
*error = IED_ERROR_OK;
uint32_t invokeId = 0;
char domainId[65];
char itemId[130];
if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;;
}
char* itemIdStart = goCB->objectReference + strlen(domainId) + 1;
char* separator = strchr(itemIdStart, '.');
if (separator == NULL)
{
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto exit_function;
}
int separatorOffset = separator - itemIdStart;
memcpy(itemId, itemIdStart, separatorOffset);
itemId[separatorOffset] = '$';
itemId[separatorOffset + 1] = 'G';
itemId[separatorOffset + 2] = 'O';
itemId[separatorOffset + 3] = '$';
itemId[separatorOffset + 4] = 0;
StringUtils_appendString(itemId, 130, separator + 1);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: setGoCBValues for %s\n", goCB->objectReference);
int itemIdLen = strlen(itemId);
/* create the list of requested itemIds references */
LinkedList itemIds = LinkedList_create();
LinkedList values = LinkedList_create();
/* add rGoEna as last element */
if (parametersMask & GOCB_ELEMENT_GO_ID)
{
StringUtils_appendString(itemId, 130, "$GoID");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->goID);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_DATSET)
{
StringUtils_appendString(itemId, 130, "$DatSet");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->datSet);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_CONF_REV)
{
StringUtils_appendString(itemId, 130, "$ConfRev");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->confRev);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_NDS_COMM)
{
StringUtils_appendString(itemId, 130, "$NdsCom");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->ndsCom);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_DST_ADDRESS)
{
StringUtils_appendString(itemId, 130, "$DstAddress");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->dstAddress);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_MIN_TIME)
{
StringUtils_appendString(itemId, 130, "$MinTime");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->minTime);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_MAX_TIME)
{
StringUtils_appendString(itemId, 130, "$MaxTime");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->maxTime);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_FIXED_OFFS)
{
StringUtils_appendString(itemId, 130, "$FixedOffs");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->fixedOffs);
itemId[itemIdLen] = 0;
}
if (parametersMask & GOCB_ELEMENT_GO_ENA)
{
StringUtils_appendString(itemId, 130, "$GoEna");
LinkedList_add(itemIds, StringUtils_copyString(itemId));
LinkedList_add(values, goCB->goEna);
itemId[itemIdLen] = 0;
}
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->specificParameter = goCB;
MmsError err;
if (singleRequest)
{
MmsConnection_writeMultipleVariablesAsync(self->connection, &(call->invokeId), &err, domainId, itemIds, values, writeMultipleVariablesHandler, self);
*error = iedConnection_mapMmsErrorToIedError(err);
if (err != MMS_ERROR_NONE)
{
iedConnection_releaseOutstandingCall(self, call);
}
else
{
invokeId = call->invokeId;
}
goto exit_function;
}
else
{
struct sWriteGoCBVariablesParameter* param = (struct sWriteGoCBVariablesParameter*) GLOBAL_MALLOC(sizeof(struct sWriteGoCBVariablesParameter));
call->specificParameter2.pointer = param;
param->itemIds = itemIds;
param->values = values;
param->currentItemId = LinkedList_getNext(itemIds);
param->currentValue = LinkedList_getNext(values);
param->domainId = StringUtils_copyString(domainId);
char* variableId = (char*)LinkedList_getData(param->currentItemId);
MmsValue* value = (MmsValue*)LinkedList_getData(param->currentValue);
MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &err, domainId, variableId, value, writeVariableHandler, self);
param->originalInvokeId = call->invokeId;
invokeId = call->invokeId;
*error = iedConnection_mapMmsErrorToIedError(err);
if (err != MMS_ERROR_NONE)
{
iedConnection_releaseOutstandingCall(self, call);
GLOBAL_FREEMEM(param->domainId);
GLOBAL_FREEMEM(param);
goto exit_function;
}
else
return invokeId;
}
exit_function:
LinkedList_destroy(itemIds);
LinkedList_destroyStatic(values);
return invokeId;
}