diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 26221b4d..a417ab0a 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -1,7 +1,7 @@ /* * IEC61850ClientAPI.cs * - * Copyright 2014-2019 Michael Zillgith + * Copyright 2014-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -625,13 +625,31 @@ namespace IEC61850 int fc, IedConnection_GetVariableSpecificationHandler handler, IntPtr parameter); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void IedConnection_ReadDataSetHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr dataSet); + private delegate void IedConnection_ReadDataSetHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSet); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_readDataSetValuesAsync(IntPtr self, out int error, string dataSetReference, IntPtr dataSet, IedConnection_ReadDataSetHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 + IedConnection_createDataSetAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet, + IedConnection_GenericServiceHandler handler, IntPtr parameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 + IedConnection_deleteDataSetAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, + IedConnection_GenericServiceHandler handler, IntPtr parameter); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void IedConnection_GetDataSetDirectoryHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSetDirectory, [MarshalAs(UnmanagedType.I1)] bool isDeletable); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 + IedConnection_getDataSetDirectoryAsync(IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, + IedConnection_GetDataSetDirectoryHandler handler, IntPtr parameter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IedConnection_GetRCBValuesHandler(UInt32 invokeId,IntPtr parameter,int err,IntPtr rcb); @@ -1742,9 +1760,6 @@ namespace IEC61850 return invokeId; } - - - /// /// Abort (close) the connection. /// @@ -2004,6 +2019,47 @@ namespace IEC61850 } + /// + /// Create a new data set - asynchronous version. + /// + /// This function creates a new data set at the server. The data set consists of the members defined + /// by the list of object references provided. + /// The object reference of the data set + /// A list of object references of the data set elements + /// Callback function to handle the received response or service timeout + /// User provided callback parameter. Will be passed to the callback function + /// the invoke ID of the sent request + /// This exception is thrown if there is a connection or service error + public UInt32 CreateDataSetAsync(string dataSetReference, List dataSetElements, GenericServiceHandler handler, object parameter) + { + int error = 0; + + IntPtr linkedList = LinkedList_create(); + + foreach (string dataSetElement in dataSetElements) + { + IntPtr dataSetElementHandle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(dataSetElement); + + LinkedList_add(linkedList, dataSetElementHandle); + } + + Tuple callbackInfo = Tuple.Create(handler, parameter); + + GCHandle handle = GCHandle.Alloc(callbackInfo); + + UInt32 invokeId = IedConnection_createDataSetAsync(connection, out error, dataSetReference, linkedList, nativeGenericServiceHandler, GCHandle.ToIntPtr(handle)); + + LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction)); + + if (error != 0) + { + handle.Free(); + throw new IedConnectionException("Create data set failed", error); + } + + return invokeId; + } + /// /// Delete a data set. /// @@ -2024,6 +2080,35 @@ namespace IEC61850 return isDeleted; } + /// + /// Delete a data set - asynchronous version. + /// + /// This function will delete a data set at the server. This function may fail if the data set is not + /// deletable. + /// The object reference of the data set + /// Callback function to handle the received response or service timeout + /// User provided callback parameter. Will be passed to the callback function + /// the invoke ID of the sent request + /// This exception is thrown if there is a connection or service error + public UInt32 DeleteDataSetAsync(string dataSetReference, GenericServiceHandler handler, object parameter) + { + int error = 0; + + Tuple callbackInfo = Tuple.Create(handler, parameter); + + GCHandle handle = GCHandle.Alloc(callbackInfo); + + UInt32 invokeId = IedConnection_deleteDataSetAsync(connection, out error, dataSetReference, nativeGenericServiceHandler, GCHandle.ToIntPtr(handle)); + + if (error != 0) + { + handle.Free(); + throw new IedConnectionException("Delete data set failed", error); + } + + return invokeId; + } + /// /// Get the directory of the data set. /// @@ -2073,6 +2158,72 @@ namespace IEC61850 return newList; } + /// + /// Get data set directory handler. + /// + /// The invoke ID of the reqeust triggering this callback + /// user provided callback parameter + /// Error code of response or timeout error in case of a response timeout + /// the list of data set entry references + /// data set can be deleted by a client (dynamic data set) + public delegate void GetDataSetDirectoryHandler(UInt32 invokeId, object parameter, IedClientError err, List dataSetDirectory, bool isDeletable); + + private void nativeGetDataSetDirectoryHandler(UInt32 invokeId, IntPtr parameter, int err, IntPtr dataSetDirectory, bool isDeletable) + { + GCHandle handle = GCHandle.FromIntPtr(parameter); + + Tuple callbackInfo = handle.Target as Tuple; + + GetDataSetDirectoryHandler handler = callbackInfo.Item1; + object handlerParameter = callbackInfo.Item2; + + IntPtr element = LinkedList_getNext(dataSetDirectory); + + handle.Free(); + + List newList = new List(); + + while (element != IntPtr.Zero) + { + string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); + + newList.Add(dataObject); + + element = LinkedList_getNext(element); + } + + LinkedList_destroy(dataSetDirectory); + + handler.Invoke(invokeId, handlerParameter, (IedClientError)err, newList, isDeletable); + } + + /// + /// Read the data set directory - asynchronous version + /// + /// The data set directory async. + /// Data set reference. + /// Callback function to handle the received response or service timeout + /// User provided callback parameter. Will be passed to the callback function + /// the invoke ID of the sent request + /// This exception is thrown if there is a connection or service error + public UInt32 GetDataSetDirectoryAsync(string dataSetReference, GetDataSetDirectoryHandler handler, object parameter) + { + int error = 0; + + Tuple callbackInfo = Tuple.Create(handler, parameter); + + GCHandle handle = GCHandle.Alloc(callbackInfo); + + UInt32 invokeId = IedConnection_getDataSetDirectoryAsync(connection, out error, dataSetReference, nativeGetDataSetDirectoryHandler, GCHandle.ToIntPtr(handle)); + + if (error != 0) + { + handle.Free(); + throw new IedConnectionException("Get data set directory failed", error); + } + + return invokeId; + } /// /// Read object handler. diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 7d31dad5..0bb73ebb 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -3009,7 +3009,6 @@ void IedConnection_createDataSet(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList /* */dataSetElements) { - char domainIdBuffer[65]; char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1]; @@ -3153,6 +3152,250 @@ 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 = strlen(dataSetReference); + + if (dataSetReference[0] != '@') { + if ((dataSetReference[0] == '/') + || (strchr(dataSetReference, '/') == NULL)) { + domainId = NULL; + + if (dataSetReference[0] == '/') + strcpy(itemId, dataSetReference + 1); + else + strcpy(itemId, 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; + } + + strcpy(itemId, dataSetReference + 1); + + isAssociationSpecific = true; + } + + MmsError mmsError; + + 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; + + 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) +{ + uint32_t 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 = 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; + + 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; + + 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); + } + + invokeId = call->invokeId; + + *error = iedConnection_mapMmsErrorToIedError(mmsError); + +cleanup_list: + /* delete list and all elements */ + LinkedList_destroyDeep(dataSetEntries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy); + +exit_function: + return invokeId; +} + LinkedList /* */ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, const char* dataSetReference, bool* isDeletable) { @@ -3219,8 +3462,8 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con dataSetMembers = LinkedList_create(); - while (entry != NULL) { - MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*) entry->data; + while (entry) { + MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*)LinkedList_getData(entry); char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec); @@ -3241,6 +3484,120 @@ exit_function: return dataSetMembers; } +static void +getDataSetDirectoryAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, LinkedList /* */ 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); + } + + if (specs) + LinkedList_destroyDeep(specs, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy); +} + +uint32_t +IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error, const char* dataSetReference, + IedConnection_GetDataSetDirectoryHandler handler, void* parameter) +{ + uint32_t invokeId = 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; + + 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 = MMS_ERROR_NONE; + + + 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: + return invokeId; +} + ClientDataSet IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, ClientDataSet dataSet) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 01a9cb95..dd1b1848 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1,7 +1,7 @@ /* * iec61850_client.h * - * Copyright 2013-2019 Michael Zillgith + * Copyright 2013-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -1771,6 +1771,31 @@ IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, LIB61850_API void IedConnection_createDataSet(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList /* char* */ dataSetElements); +/** + * \brief create a new data set at the connected server device + * + * This function creates a new data set at the server. The parameter dataSetReference is the name of the new data set + * to create. It is either in the form LDName/LNodeName.dataSetName for permanent domain or VMD scope data sets or + * @dataSetName for an association specific data set. If the LDName part of the reference is missing the resulting + * data set will be of VMD scope. + * + * The dataSetElements parameter contains a linked list containing the object references of FCDs or FCDAs. The format of + * this object references is LDName/LNodeName.item(arrayIndex)component[FC]. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param dataSetElements a list of object references defining the members of the new data set + * + * \param handler the callback handler that is called when the response is received or timeout + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ +LIB61850_API uint32_t +IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList /* char* */ dataSetElements, + IedConnection_GenericServiceHandler handler, void* parameter); + /** * \brief delete a deletable data set at the connected server device * @@ -1787,9 +1812,29 @@ IedConnection_createDataSet(IedConnection self, IedClientError* error, const cha LIB61850_API bool IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const char* dataSetReference); +/** + * \brief delete a deletable data set at the connected server device - asynchronous version + * + * This function deletes a data set at the server. The parameter dataSetReference is the name of the data set + * to delete. It is either in the form LDName/LNodeName.dataSetName or @dataSetName for an association specific data set. + * + * The data set was deleted successfully when the callback parameter "error" is IED_ERROR_OK. Otherwise the "error" + * parameter contains a particular error code. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param handler the callback handler that is called when the response is received or timeout + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ +LIB61850_API uint32_t +IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, const char* dataSetReference, + IedConnection_GenericServiceHandler handler, void* parameter); /** - * \brief returns the object references of the elements of a data set + * \brief read the data set directory * * The return value contains a linked list containing the object references of FCDs or FCDAs. The format of * this object references is LDName/LNodeName.item(arrayIndex)component[FC]. @@ -1805,6 +1850,34 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha LIB61850_API LinkedList /* */ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, const char* dataSetReference, bool* isDeletable); +/** + * \brief GetDataSetDirectory response or timeout callback + * + * \param dataSetDirectory a linked list containing the object references of FCDs or FCDAs. The format of + * this object references is LDName/LNodeName.item(arrayIndex)component[FC]. + * \param isDeletable this is an output parameter indicating that the requested data set is deletable by clients. + */ +typedef void +(*IedConnection_GetDataSetDirectoryHandler) (uint32_t invokeId, void* parameter, IedClientError err, LinkedList /* */ dataSetDirectory, bool isDeletable); + +/** + * \brief read the data set directory - asynchronous version + * + * The result data is a linked list containing the object references of FCDs or FCDAs. The format of + * this object references is LDName/LNodeName.item(arrayIndex)component[FC]. + * + * \param connection the connection object + * \param[out] error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param handler the callback handler that is called when the response is received or timeout + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ +LIB61850_API uint32_t +IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error, const char* dataSetReference, + IedConnection_GetDataSetDirectoryHandler handler, void* parameter); + /** * \brief Write the data set values to the server * diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 4dd10ded..57243230 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -291,7 +291,7 @@ mmsClient_createDeleteNamedVariableListRequest(long invokeId, ByteBuffer* writeB const char* domainId, const char* listNameId); LIB61850_INTERNAL bool -mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* invokeId); +mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* invokeId, long* numberDeleted, long* numberMatched); LIB61850_INTERNAL void mmsClient_createDeleteAssociationSpecificNamedVariableListRequest( diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index e4bd2ae5..d7eacfd0 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -804,9 +804,19 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M else { bool success = false; - if (mmsClient_parseDeleteNamedVariableListResponse(response, NULL)) + long numberMatched = 0; + long numberDeleted = 0; + + if (mmsClient_parseDeleteNamedVariableListResponse(response, NULL, &numberDeleted, &numberMatched)) success = true; + if (numberMatched == 0) + err = MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT; + else { + if (numberDeleted == 0) + err = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + } + handler(outstandingCall->invokeId, outstandingCall->userParameter, err, success); } } diff --git a/src/mms/iso_mms/client/mms_client_named_variable_list.c b/src/mms/iso_mms/client/mms_client_named_variable_list.c index 3ca329e1..74a0da1b 100644 --- a/src/mms/iso_mms/client/mms_client_named_variable_list.c +++ b/src/mms/iso_mms/client/mms_client_named_variable_list.c @@ -117,7 +117,7 @@ mmsClient_createDeleteAssociationSpecificNamedVariableListRequest( } bool -mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* invokeId) +mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* invokeId, long* numberDeleted, long* numberMatched) { MmsPdu_t* mmsPdu = 0; @@ -138,11 +138,10 @@ mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* in DeleteNamedVariableListResponse_t* response = &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.deleteNamedVariableList); - long numberDeleted; + asn_INTEGER2long(&(response->numberDeleted), numberDeleted); + asn_INTEGER2long(&(response->numberMatched), numberMatched); - asn_INTEGER2long(&(response->numberDeleted), &numberDeleted); - - if (numberDeleted == 1) + if (*numberDeleted == 1) retVal = true; } }