You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4259 lines
122 KiB
C
4259 lines
122 KiB
C
/*
|
|
* ied_connection.c
|
|
*
|
|
* Copyright 2013-2022 Michael Zillgith
|
|
*
|
|
* This file is part of libIEC61850.
|
|
*
|
|
* libIEC61850 is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* libIEC61850 is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* See COPYING file for the complete license text.
|
|
*/
|
|
|
|
#include "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! */
|
|
#define OUTSTANDING_CALLS 12
|
|
|
|
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;
|
|
};
|
|
|
|
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;
|
|
|
|
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 < OUTSTANDING_CALLS; 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 < OUTSTANDING_CALLS; 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));
|
|
|
|
self->name = StringUtils_copyString(name);
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
ICLogicalDevice_setVariableList(ICLogicalDevice* self, LinkedList variables)
|
|
{
|
|
self->variables = variables;
|
|
}
|
|
|
|
static void
|
|
ICLogicalDevice_destroy(ICLogicalDevice* self)
|
|
{
|
|
GLOBAL_FREEMEM(self->name);
|
|
|
|
if (self->variables != NULL)
|
|
LinkedList_destroy(self->variables);
|
|
|
|
GLOBAL_FREEMEM(self);
|
|
}
|
|
|
|
static ClientDataSet
|
|
ClientDataSet_create(const char* dataSetReference)
|
|
{
|
|
ClientDataSet self = (ClientDataSet) GLOBAL_CALLOC(1, sizeof(struct sClientDataSet));
|
|
|
|
self->dataSetReference = StringUtils_copyString(dataSetReference);
|
|
StringUtils_replace(self->dataSetReference, '.', '$');
|
|
|
|
self->dataSetValues = NULL;
|
|
|
|
return self;
|
|
}
|
|
|
|
void
|
|
ClientDataSet_destroy(ClientDataSet self)
|
|
{
|
|
if (self->dataSetValues != NULL)
|
|
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 != NULL) {
|
|
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(char* domainName, 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 != NULL)
|
|
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->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(OUTSTANDING_CALLS, 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_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);
|
|
}
|
|
}
|
|
|
|
void
|
|
IedConnection_destroy(IedConnection self)
|
|
{
|
|
IedConnection_close(self);
|
|
|
|
MmsConnection_destroy(self->connection);
|
|
|
|
if (self->logicalDevices != NULL)
|
|
LinkedList_destroyDeep(self->logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy);
|
|
|
|
if (self->enabledReports != NULL)
|
|
LinkedList_destroyDeep(self->enabledReports, (LinkedListValueDeleteFunction) ClientReport_destroy);
|
|
|
|
GLOBAL_FREEMEM(self->outstandingCalls);
|
|
|
|
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 != NULL)
|
|
*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 != NULL)
|
|
*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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
if (MmsValue_getType(value) == MMS_UTC_TIME) {
|
|
|
|
if (retVal == NULL)
|
|
retVal = (Timestamp*) GLOBAL_MALLOC(sizeof(Timestamp));
|
|
|
|
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 != NULL) {
|
|
|
|
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 != NULL) {
|
|
|
|
if (self->logicalDevices != NULL) {
|
|
LinkedList_destroyDeep(self->logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy);
|
|
self->logicalDevices = NULL;
|
|
}
|
|
|
|
LinkedList logicalDevice = LinkedList_getNext(logicalDeviceNames);
|
|
|
|
LinkedList logicalDevices = LinkedList_create();
|
|
|
|
while (logicalDevice != NULL) {
|
|
char* name = (char*) logicalDevice->data;
|
|
|
|
LinkedList variables = MmsConnection_getDomainVariableNames(self->connection,
|
|
&mmsError, name);
|
|
|
|
if (variables != NULL) {
|
|
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
|
|
*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 != NULL) {
|
|
LinkedList logicalDevice = LinkedList_getNext(self->logicalDevices);
|
|
|
|
LinkedList logicalDeviceList = LinkedList_create();
|
|
|
|
while (logicalDevice != NULL) {
|
|
ICLogicalDevice* icLogicalDevice = (ICLogicalDevice*) logicalDevice->data;
|
|
|
|
char* logicalDeviceName = StringUtils_copyString(icLogicalDevice->name);
|
|
|
|
LinkedList_add(logicalDeviceList, logicalDeviceName);
|
|
|
|
logicalDevice = LinkedList_getNext(logicalDevice);
|
|
}
|
|
|
|
*error = IED_ERROR_OK;
|
|
return logicalDeviceList;
|
|
}
|
|
else {
|
|
*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 != NULL)
|
|
*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 != NULL) {
|
|
ICLogicalDevice* device = (ICLogicalDevice*) logicalDevice->data;
|
|
|
|
if (strcmp(device->name, logicalDeviceName) == 0) {
|
|
LinkedList logicalNodeNames = LinkedList_create();
|
|
|
|
LinkedList variable = LinkedList_getNext(device->variables);
|
|
|
|
while (variable != NULL) {
|
|
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) != NULL) {
|
|
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 != NULL) {
|
|
char* variableName = (char*) variable->data;
|
|
|
|
char* fcPos = strchr(variableName, '$');
|
|
|
|
if (fcPos != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
|
|
char* journalName = (char*) LinkedList_getData(journal);
|
|
|
|
char* logName = strchr(journalName, '$');
|
|
|
|
if (logName != NULL) {
|
|
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 != NULL) {
|
|
char* dataSetName = (char*) LinkedList_getData(dataSet);
|
|
|
|
char* lnDataSetName = strchr(dataSetName, '$');
|
|
|
|
if (lnDataSetName != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
char* variableName = (char*) variable->data;
|
|
|
|
char* fcPos = strchr(variableName, '$');
|
|
|
|
if (fcPos != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
char* variableName = (char*) variable->data;
|
|
|
|
char* fcPos = strchr(variableName, '$');
|
|
|
|
if (fcPos != NULL) {
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
char* variableName = (char*) variable->data;
|
|
|
|
char* fcPos = strchr(variableName, '$');
|
|
|
|
if (fcPos != NULL) {
|
|
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 != NULL)
|
|
goto next_variable;
|
|
|
|
char* elementName;
|
|
|
|
if (withFc) {
|
|
int elementNameLen = (int)strlen(subElementName);
|
|
|
|
elementName = (char*) GLOBAL_MALLOC(elementNameLen + 5);
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
char* variableName = (char*) variable->data;
|
|
|
|
char* fcPos = strchr(variableName, '$');
|
|
|
|
if (fcPos != NULL) {
|
|
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 != NULL)
|
|
goto next_variable;
|
|
|
|
int elementNameLen = (int)strlen(subElementName);
|
|
|
|
char* elementName = (char*) GLOBAL_MALLOC(elementNameLen + 1);
|
|
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 != NULL) {
|
|
|
|
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 != NULL) {
|
|
|
|
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) {
|
|
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 != NULL)
|
|
*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, value);
|
|
}
|
|
else {
|
|
MmsValue* dataSetValues = ClientDataSet_getValues(dataSet);
|
|
MmsValue_update(dataSetValues, value);
|
|
}
|
|
|
|
if (dataSetReference)
|
|
GLOBAL_FREEMEM(dataSetReference);
|
|
|
|
MmsValue_delete(value);
|
|
}
|
|
|
|
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 != NULL) {
|
|
|
|
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 != NULL) {
|
|
|
|
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 != NULL) {
|
|
|
|
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 != NULL) {
|
|
|
|
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));
|
|
|
|
self->fileName = StringUtils_copyString(fileName);
|
|
self->fileSize = fileSize;
|
|
self->lastModified = lastModified;
|
|
|
|
return self;
|
|
}
|
|
|
|
void
|
|
FileDirectoryEntry_destroy(FileDirectoryEntry 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;
|
|
}
|
|
|