From d0ac21e487081b2d1e6d9d3997132c3fb940f250 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 25 Aug 2017 00:02:51 +0200 Subject: [PATCH] - client/server: added set data set service (MMS write named variable list) - client/server: improved write access to array elements and sub arrays --- CMakeLists.txt | 4 +- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 53 ++ .../inc/libiec61850_platform_includes.h | 2 +- src/doxygen.config | 2 +- src/iec61850/client/ied_connection.c | 58 ++ src/iec61850/inc/iec61850_client.h | 26 + src/mms/inc/mms_client_connection.h | 52 +- src/mms/inc/mms_value.h | 16 +- src/mms/inc_private/mms_client_internal.h | 11 +- src/mms/inc_private/mms_server_internal.h | 4 +- .../iso_mms/client/mms_client_connection.c | 69 +- src/mms/iso_mms/client/mms_client_journals.c | 2 +- src/mms/iso_mms/client/mms_client_write.c | 285 ++++++- src/mms/iso_mms/server/mms_access_result.c | 21 +- src/mms/iso_mms/server/mms_read_service.c | 36 +- .../iso_mms/server/mms_server_connection.c | 117 ++- src/mms/iso_mms/server/mms_write_service.c | 724 ++++++++++++++---- src/sampled_values/sv_subscriber.c | 51 ++ src/sampled_values/sv_subscriber.h | 20 + src/vs/libiec61850-wo-goose.def | 3 +- src/vs/libiec61850.def | 5 +- 21 files changed, 1316 insertions(+), 245 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd880bb..f398a87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ project(libiec61850) ENABLE_TESTING() set(LIB_VERSION_MAJOR "1") -set(LIB_VERSION_MINOR "0") -set(LIB_VERSION_PATCH "2") +set(LIB_VERSION_MINOR "1") +set(LIB_VERSION_PATCH "0") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 98d1bad..aef42ee 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -335,6 +335,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_readDataSetValues (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_writeDataSetValues (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr values, out IntPtr accessResults); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_createDataSet (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet); @@ -1241,6 +1244,56 @@ namespace IEC61850 return dataSet; } + /// + /// Writes the values of a data set (SetDataSetValues service). + /// + /// The list of access results + /// The object reference of the data set + /// The new values for the data set members. The values have to be of the same number and type as the data set members + public List WriteDataSetValues(string dataSetReference, List values) + { + int error; + IntPtr accessResults = IntPtr.Zero; + + IntPtr valueList = LinkedList_create (); + + foreach (MmsValue mmsValue in values) { + LinkedList_add (valueList, mmsValue.valueReference); + } + + IedConnection_writeDataSetValues (connection, out error, dataSetReference, valueList, out accessResults); + + LinkedList_destroyStatic (valueList); + + /* handle access results */ + + List accessResultList = null; + + if (accessResults != IntPtr.Zero) { + + IntPtr element = LinkedList_getNext (accessResults); + + while (element != IntPtr.Zero) { + IntPtr elementData = LinkedList_getData (element); + + MmsValue accessResultValue = new MmsValue (elementData, true); + + MmsDataAccessError dataAccessError = accessResultValue.GetDataAccessError (); + + accessResultList.Add (dataAccessError); + + element = LinkedList_getNext (element); + } + + LinkedList_destroyStatic (accessResults); + } + + if (error != 0) + throw new IedConnectionException ("Writing data set failed", error); + + return accessResultList; + } + /// /// Create a new data set. /// diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index 4dc01c2..fcc50c6 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "1.0.2" +#define LIBIEC61850_VERSION "1.1" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" diff --git a/src/doxygen.config b/src/doxygen.config index f188266..2970a10 100644 --- a/src/doxygen.config +++ b/src/doxygen.config @@ -18,7 +18,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "libIEC61850" -PROJECT_NUMBER = 1.0.2 +PROJECT_NUMBER = 1.1 PROJECT_BRIEF = "Open-source IEC 61850 MMS/GOOSE/SV server and client library" diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index ac91bf6..fdad55d 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -2326,6 +2326,64 @@ exit_function: return dataSet; } +void +IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, + LinkedList/**/ values, /* OUTPUT */LinkedList* /* */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; +} + LinkedList /* */ IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, uint64_t startTime, uint64_t endTime, bool* moreFollows) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 254abb6..d319c10 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1321,6 +1321,12 @@ IedConnection_writeOctetString(IedConnection self, IedClientError* error, const /** * \brief get data set values from a server device * + * The parameter dataSetReference is the name of the data set to read. 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 received data set values are stored in a container object of type ClientDataSet that can be reused in a further + * read request. + * * \param connection the connection object * \param error the error code if an error occurs * \param dataSetReference object reference of the data set @@ -1386,6 +1392,26 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha LinkedList /* */ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, const char* dataSetReference, bool* isDeletable); +/** + * \brief Write the data set values to the server + * + * The parameter dataSetReference is the name of the data set to write. 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 values parameter has to be the same number of elements as are members in the data set. The accessResult return parameter + * contains a value for each data set member. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param values the new data set values + * \param accessResults the access results for each data set member + */ +void +IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, + LinkedList/**/ values, /* OUTPUT */LinkedList* /* */accessResults); + + /******************************************************** * Data set object (local representation of a data set) *******************************************************/ diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 1f60c1c..48c9c8e 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -399,20 +399,47 @@ MmsConnection_readMultipleVariables(MmsConnection self, MmsError* mmsError, cons /** * \brief Write a single variable to the server. * + * NOTE: added return value in version 1.1 + * * \param self MmsConnection instance to operate on * \param mmsError user provided variable to store error code * \param domainId the domain name of the variable to be written * \param itemId name of the variable to be written * \param value value of the variable to be written + * + * \return when successful, the data access error value returned by the server */ -void +MmsDataAccessError MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, MmsValue* value); +/** + * \brief Write a single array element or a sub array to an array type variable + * + * When a single array element is address the MmsValue object value has to be of the type + * of the array elements. When multiple array elements have to be written (index range) the + * MmsValue object value has to be of type MMS_ARRAY containing "numberOfElements" elements. + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param domainId the domain name of the variable to be written + * \param index the index of the array element or the start index of a index range + * \param numberOfElements the number of array elements to write starting with index. If 0 only one array element is written. + * \param itemId name of the variable to be written + * \param value value of the array element(s) to be written. Has to be of the type of + * the array elements or of type MMS_ARRAY when it is a sub array (index range) + * + * \return when successful, the data access error value returned by the server + */ +MmsDataAccessError +MmsConnection_writeArrayElements(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, int index, int numberOfElements, + MmsValue* value); + /** * \brief Write multiple variables to the server. * - * This function will write multiple variables at the server. + * This function will write multiple variables to the server. * * The parameter accessResults is a pointer to a LinkedList reference. The methods will create a new LinkedList * object that contains the AccessResults of the single variable write attempts. It is up to the user to free this @@ -431,6 +458,27 @@ MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* mmsError, con LinkedList /**/ items, LinkedList /* */ values, LinkedList* /* */ accessResults); +/** + * \brief Write named variable list values to the server. + * + * The parameter accessResults is a pointer to a LinkedList reference. The methods will create a new LinkedList + * object that contains the AccessResults of the single variable write attempts. It is in the responsibility of + * the user to free this objects properly (e.g. with LinkedList_destroyDeep(accessResults, MmsValue_delete)). + * If accessResult is the to NULL the result will not be stored. + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param isAssociationSpecifc true if the named variable list is an association specific named variable list + * \param domainId the common domain name of all variables to be written + * \param values values of the variables to be written + * \param (OUTPUT) the MmsValue objects of type MMS_DATA_ACCESS_ERROR representing the write success of a single variable + * write. + */ +void +MmsConnection_writeNamedVariableList(MmsConnection self, MmsError* mmsError, bool isAssociationSpecific, + const char* domainId, const char* itemId, LinkedList /* */values, + /* OUTPUT */LinkedList* /* */accessResults); + /** * \brief Get the variable access attributes of a MMS named variable of the server * diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index 61253fb..7108200 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -953,14 +953,28 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize); /** * \brief create a new MmsValue instance from a BER encoded MMS Data element (deserialize) * + * WARNING: API changed with version 1.0.3 (added endBufPos parameter) + * + * \param buffer the buffer to read from + * \param bufPos the start position of the mms value data in the buffer + * \param bufferLength the length of the buffer + * \param endBufPos the position in the buffer after the read MMS data element (NULL if not required) + * + * \return the MmsValue instance created from the buffer */ MmsValue* -MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength); +MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBufPos); /** * \brief Serialize the MmsValue instance as BER encoded MMS Data element * * \param self the MmsValue instance + * + * \param buffer the buffer to encode the MMS data element + * \param bufPos the position in the buffer where to start encoding + * \param encode encode to buffer (true) or calculate length only (false) + * + * \return the encoded length of the corresponding MMS data element */ int MmsValue_encodeMmsData(MmsValue* self, uint8_t* buffer, int bufPos, bool encode); diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index bc52d6a..8625332 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -195,7 +195,7 @@ mmsClient_createGetVariableAccessAttributesRequest( MmsVariableSpecification* mmsClient_parseGetVariableAccessAttributesResponse(ByteBuffer* message, uint32_t* invokeId); -void +MmsDataAccessError mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsError); void @@ -210,6 +210,15 @@ int mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, const char* domainId, LinkedList itemIds, LinkedList values, ByteBuffer* writeBuffer); +int +mmsClient_createWriteRequestNamedVariableList(uint32_t invokeId, bool isAssociationSpecific, const char* domainId, const char* itemId, + LinkedList values, ByteBuffer* writeBuffer); + +int +mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const char* itemId, + int startIndex, int elementCount, + MmsValue* value, ByteBuffer* writeBuffer); + void mmsClient_createDefineNamedVariableListRequest(uint32_t invokeId, ByteBuffer* writeBuffer, const char* domainId, const char* listNameId, LinkedList /**/ listOfVariables, diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 58655c8..6c8df61 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -370,9 +370,9 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va MmsValue* mmsServer_getValue(MmsServer self, MmsDomain* domain, char* itemId, MmsServerConnection connection); -int +void mmsServer_createMmsWriteResponse(MmsServerConnection connection, - int invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults); + uint32_t invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults); void mmsMsg_createMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response); diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 84743ea..128c810 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -2166,11 +2166,13 @@ MmsConnection_obtainFile(MmsConnection self, MmsError* mmsError, const char* sou *mmsError = MMS_ERROR_CONNECTION_LOST; } -void +MmsDataAccessError MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, MmsValue* value) { + MmsDataAccessError retVal = DATA_ACCESS_ERROR_UNKNOWN; + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); *mmsError = MMS_ERROR_NONE; @@ -2184,12 +2186,14 @@ MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, if (self->lastResponseError != MMS_ERROR_NONE) *mmsError = self->lastResponseError; else if (responseMessage != NULL) - mmsClient_parseWriteResponse(self->lastResponse, self->lastResponseBufPos, mmsError); + retVal = mmsClient_parseWriteResponse(self->lastResponse, self->lastResponseBufPos, mmsError); releaseResponse(self); if (self->associationState == MMS_STATE_CLOSED) *mmsError = MMS_ERROR_CONNECTION_LOST; + + return retVal; } void @@ -2224,6 +2228,67 @@ MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* mmsError, con *mmsError = MMS_ERROR_CONNECTION_LOST; } +MmsDataAccessError +MmsConnection_writeArrayElements(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, int index, int numberOfElements, + MmsValue* value) +{ + MmsDataAccessError retVal = DATA_ACCESS_ERROR_UNKNOWN; + + *mmsError = MMS_ERROR_NONE; + + uint32_t invokeId = getNextInvokeId(self); + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + mmsClient_createWriteRequestArray(invokeId, domainId, itemId, index, numberOfElements, value, payload); + + ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); + + if (self->lastResponseError != MMS_ERROR_NONE) + *mmsError = self->lastResponseError; + else if (responseMessage != NULL) + retVal = mmsClient_parseWriteResponse(self->lastResponse, self->lastResponseBufPos, mmsError); + + releaseResponse(self); + + if (self->associationState == MMS_STATE_CLOSED) + *mmsError = MMS_ERROR_CONNECTION_LOST; + + return retVal; +} + +void +MmsConnection_writeNamedVariableList(MmsConnection self, MmsError* mmsError, bool isAssociationSpecific, + const char* domainId, const char* itemId, LinkedList /* */values, + /* OUTPUT */LinkedList* /* */accessResults) +{ + *mmsError = MMS_ERROR_NONE; + + uint32_t invokeId = getNextInvokeId(self); + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + mmsClient_createWriteRequestNamedVariableList(invokeId, isAssociationSpecific, domainId, itemId, values, payload); + + ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); + + if (self->lastResponseError != MMS_ERROR_NONE) + *mmsError = self->lastResponseError; + else if (responseMessage != NULL) { + + int numberOfItems = LinkedList_size(values); + + mmsClient_parseWriteMultipleItemsResponse(self->lastResponse, self->lastResponseBufPos, mmsError, + numberOfItems, accessResults); + } + + releaseResponse(self); + + if (self->associationState == MMS_STATE_CLOSED) + *mmsError = MMS_ERROR_CONNECTION_LOST; +} + void MmsServerIdentity_destroy(MmsServerIdentity* self) { diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index 919e2c5..cf59685 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -67,7 +67,7 @@ parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVaria case 0xa1: /* valueSpec */ if (journalVariable->value == NULL) { - journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length); + journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length, NULL); } break; diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index 8163cdd..1b7353a 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -87,7 +87,8 @@ mmsClient_parseWriteMultipleItemsResponse(ByteBuffer* message, int32_t bufPos, M return; } - *accessResults = LinkedList_create(); + if (accessResults != NULL) + *accessResults = LinkedList_create(); int endPos = bufPos + length; @@ -101,17 +102,16 @@ mmsClient_parseWriteMultipleItemsResponse(ByteBuffer* message, int32_t bufPos, M if (bufPos == -1) goto exit_with_error; if (tag == 0x81) { - MmsValue* value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_SUCCESS); - LinkedList_add(*accessResults, (void*) value); + if (accessResults != NULL) + LinkedList_add(*accessResults, (void*) MmsValue_newDataAccessError(DATA_ACCESS_ERROR_SUCCESS)); } if (tag == 0x80) { uint32_t dataAccessErrorCode = BerDecoder_decodeUint32(buf, length, bufPos); - MmsValue* value = MmsValue_newDataAccessError((MmsDataAccessError) dataAccessErrorCode); - - LinkedList_add(*accessResults, (void*) value); + if (accessResults != NULL) + LinkedList_add(*accessResults, (void*) MmsValue_newDataAccessError((MmsDataAccessError) dataAccessErrorCode)); } bufPos += length; @@ -129,13 +129,17 @@ mmsClient_parseWriteMultipleItemsResponse(ByteBuffer* message, int32_t bufPos, M exit_with_error: *mmsError = MMS_ERROR_PARSING_RESPONSE; - LinkedList_destroyDeep(*accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); + + if (accessResults != NULL) + LinkedList_destroyDeep(*accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); } -void +MmsDataAccessError mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsError) { + MmsDataAccessError retVal = DATA_ACCESS_ERROR_UNKNOWN; + uint8_t* buf = message->buffer; int size = message->size; @@ -151,13 +155,16 @@ mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsE if (bufPos == -1) { *mmsError = MMS_ERROR_PARSING_RESPONSE; - return; + retVal = DATA_ACCESS_ERROR_UNKNOWN; + goto exit_function; } tag = buf[bufPos++]; - if (tag == 0x81) - return; + if (tag == 0x81) { + retVal = DATA_ACCESS_ERROR_SUCCESS; + goto exit_function; + } if (tag == 0x80) { bufPos = BerDecoder_decodeLength(buf, &length, bufPos, size); @@ -165,26 +172,104 @@ mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsE uint32_t dataAccessErrorCode = BerDecoder_decodeUint32(buf, length, bufPos); - *mmsError = mapDataAccessErrorToMmsError(dataAccessErrorCode); + if ((dataAccessErrorCode >= 0) || (dataAccessErrorCode < 13)) { + *mmsError = mapDataAccessErrorToMmsError(dataAccessErrorCode); + retVal = (MmsDataAccessError) dataAccessErrorCode; + } + else { + *mmsError = MMS_ERROR_PARSING_RESPONSE; + retVal = DATA_ACCESS_ERROR_UNKNOWN; + } } } else *mmsError = MMS_ERROR_PARSING_RESPONSE; + +exit_function: + return retVal; } -static VariableSpecification_t* +//TODO remove redundant code (see mms_client_read.c) + +static AlternateAccess_t* +createAlternateAccess(uint32_t index, uint32_t elementCount) +{ + AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); + alternateAccess->list.count = 1; + alternateAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + alternateAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); + alternateAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; + + alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); + + alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; + + if (elementCount > 0) { + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = + AlternateAccessSelection__selectAccess_PR_indexRange; + + INTEGER_t* asnIndex = + &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex); + + asn_long2INTEGER(asnIndex, index); + + asnIndex = + &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements); + + asn_long2INTEGER(asnIndex, elementCount); + } + else { + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = + AlternateAccessSelection__selectAccess_PR_index; + + INTEGER_t* asnIndex = + &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index); + + asn_long2INTEGER(asnIndex, index); + } + + return alternateAccess; +} + +static void +deleteAlternateAccess(AlternateAccess_t* alternateAccess) +{ + if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex.buf != NULL) { + GLOBAL_FREEMEM(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex.buf); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex.buf = NULL; + } + + if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements.buf != NULL) { + GLOBAL_FREEMEM(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements.buf); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements.buf = NULL; + } + + if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index.buf != NULL) { + GLOBAL_FREEMEM(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index.buf); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index.buf = NULL; + } + + GLOBAL_FREEMEM(alternateAccess->list.array[0]->choice.unnamed); + GLOBAL_FREEMEM(alternateAccess->list.array[0]); + GLOBAL_FREEMEM(alternateAccess->list.array); + GLOBAL_FREEMEM(alternateAccess); + +} + + +static ListOfVariableSeq_t* createNewDomainVariableSpecification(const char* domainId, const char* itemId) { - VariableSpecification_t* varSpec = (VariableSpecification_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t)); + ListOfVariableSeq_t* varSpec = (ListOfVariableSeq_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t)); - varSpec->present = VariableSpecification_PR_name; - varSpec->choice.name.present = ObjectName_PR_domainspecific; - varSpec->choice.name.choice.domainspecific.domainId.buf = (uint8_t*) domainId; - varSpec->choice.name.choice.domainspecific.domainId.size = strlen(domainId); - varSpec->choice.name.choice.domainspecific.itemId.buf = (uint8_t*) itemId; - varSpec->choice.name.choice.domainspecific.itemId.size = strlen(itemId); + varSpec->variableSpecification.present = VariableSpecification_PR_name; + varSpec->variableSpecification.choice.name.present = ObjectName_PR_domainspecific; + varSpec->variableSpecification.choice.name.choice.domainspecific.domainId.buf = (uint8_t*) domainId; + varSpec->variableSpecification.choice.name.choice.domainspecific.domainId.size = strlen(domainId); + varSpec->variableSpecification.choice.name.choice.domainspecific.itemId.buf = (uint8_t*) itemId; + varSpec->variableSpecification.choice.name.choice.domainspecific.itemId.size = strlen(itemId); - return varSpec; + return varSpec; } static void @@ -262,7 +347,7 @@ mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, const char* domainI char* itemId = (char*) item->data; MmsValue* value = (MmsValue*) valueElement->data; - request->variableAccessSpecification.choice.listOfVariable.list.array[i] = (ListOfVariableSeq_t*) + request->variableAccessSpecification.choice.listOfVariable.list.array[i] = createNewDomainVariableSpecification(domainId, itemId); request->listOfData.list.array[i] = mmsMsg_createBasicDataElement(value); @@ -314,7 +399,7 @@ mmsClient_createWriteRequest(uint32_t invokeId, const char* domainId, const char request->variableAccessSpecification.choice.listOfVariable.list.size = 1; request->variableAccessSpecification.choice.listOfVariable.list.array = (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); - request->variableAccessSpecification.choice.listOfVariable.list.array[0] = (ListOfVariableSeq_t*) + request->variableAccessSpecification.choice.listOfVariable.list.array[0] = createNewDomainVariableSpecification(domainId, itemId); /* Create list of typed data values */ @@ -346,3 +431,157 @@ mmsClient_createWriteRequest(uint32_t invokeId, const char* domainId, const char return rval.encoded; } + +/** + * \brief Encode request to write a named variable list (SetDataSet) + * + * The named variable list can be + * - VMD specific (domainId = NULL and isAssociationSpecific = false) + * - Domain specific (domainID != NULL and isAssociationSpecific = false) + * - association specific (isAssociationSpecific = true, domainId will be ignored) + * + * \param invokeId invoke ID of the new request + * \param isAssociationSpecific true = create an association specific request, false = create domain of VMD specific request + * \param domainId name of the MMS domain or NULL for association of VMD specific request + * \param itemId named variable list name + * \param values the list of the named variable list element values + * \param writeBuffer the buffer to write the request + * + * \return number of bytes encoded + */ +int +mmsClient_createWriteRequestNamedVariableList(uint32_t invokeId, bool isAssociationSpecific, const char* domainId, const char* itemId, + LinkedList values, ByteBuffer* writeBuffer) +{ + MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); + + mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present = + ConfirmedServiceRequest_PR_write; + WriteRequest_t* request = + &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); + + /* Create list of variable specifications */ + request->variableAccessSpecification.present = VariableAccessSpecification_PR_variableListName; + + if (isAssociationSpecific) { + request->variableAccessSpecification.choice.variableListName.present = ObjectName_PR_aaspecific; + request->variableAccessSpecification.choice.variableListName.choice.aaspecific.buf = (uint8_t*) StringUtils_copyString(itemId); + request->variableAccessSpecification.choice.variableListName.choice.aaspecific.size = strlen(itemId); + } + else { + if (domainId != NULL) { + request->variableAccessSpecification.choice.variableListName.present = ObjectName_PR_domainspecific; + request->variableAccessSpecification.choice.variableListName.choice.domainspecific.domainId.buf = (uint8_t*) StringUtils_copyString(domainId); + request->variableAccessSpecification.choice.variableListName.choice.domainspecific.domainId.size = strlen(domainId); + request->variableAccessSpecification.choice.variableListName.choice.domainspecific.itemId.buf = (uint8_t*) StringUtils_copyString(itemId); + request->variableAccessSpecification.choice.variableListName.choice.domainspecific.itemId.size = strlen(itemId); + } + else { + request->variableAccessSpecification.choice.variableListName.present = ObjectName_PR_vmdspecific; + request->variableAccessSpecification.choice.variableListName.choice.vmdspecific.buf = (uint8_t*) StringUtils_copyString(itemId); + request->variableAccessSpecification.choice.variableListName.choice.vmdspecific.size = strlen(itemId); + } + } + + /* Create list of typed data values */ + + int numberOfItems = LinkedList_size(values); + + request->listOfData.list.count = numberOfItems; + request->listOfData.list.size = numberOfItems; + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(numberOfItems, sizeof(struct Data*)); + + int i; + + LinkedList valueElement = LinkedList_getNext(values); + + for (i = 0; i < numberOfItems; i++) { + + if (valueElement == NULL) return -1; + + MmsValue* value = (MmsValue*) valueElement->data; + + request->listOfData.list.array[i] = mmsMsg_createBasicDataElement(value); + + valueElement = LinkedList_getNext(valueElement); + } + + /* Encode complete ASN1 structure */ + + asn_enc_rval_t rval; + + rval = der_encode(&asn_DEF_MmsPdu, mmsPdu, + (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); + + /* Free ASN structure */ + + for (i = 0; i < numberOfItems; i++) + deleteDataElement(request->listOfData.list.array[i]); + + request->listOfData.list.count = 0; + GLOBAL_FREEMEM(request->listOfData.list.array); + request->listOfData.list.array = 0; + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + return rval.encoded; +} + +int +mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const char* itemId, + int startIndex, int elementCount, + MmsValue* value, + ByteBuffer* writeBuffer) +{ + MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); + + mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present = + ConfirmedServiceRequest_PR_write; + WriteRequest_t* request = + &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); + + /* Create list of variable specifications */ + request->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; + request->variableAccessSpecification.choice.listOfVariable.list.count = 1; + request->variableAccessSpecification.choice.listOfVariable.list.size = 1; + request->variableAccessSpecification.choice.listOfVariable.list.array = + (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); + + ListOfVariableSeq_t* variableIdentifier = createNewDomainVariableSpecification(domainId, itemId); + variableIdentifier->alternateAccess = createAlternateAccess(startIndex, elementCount); + request->variableAccessSpecification.choice.listOfVariable.list.array[0] = variableIdentifier; + + /* Create list of typed data values */ + request->listOfData.list.count = 1; + request->listOfData.list.size = 1; + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(1, sizeof(struct Data*)); + request->listOfData.list.array[0] = mmsMsg_createBasicDataElement(value); + + /* Encode complete ASN1 structure */ + + asn_enc_rval_t rval; + + rval = der_encode(&asn_DEF_MmsPdu, mmsPdu, + (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); + + /* Free ASN structure */ + deleteAlternateAccess(variableIdentifier->alternateAccess); + + request->variableAccessSpecification.choice.listOfVariable.list.count = 0; + + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[0]); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array); + request->variableAccessSpecification.choice.listOfVariable.list.array = 0; + + + request->listOfData.list.count = 0; + + deleteDataElement(request->listOfData.list.array[0]); + + GLOBAL_FREEMEM(request->listOfData.list.array); + request->listOfData.list.array = 0; + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + return rval.encoded; +} diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index 0e09395..0dafbf7 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -152,7 +152,7 @@ exit_with_error: } MmsValue* -MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) +MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBufPos) { MmsValue* value = NULL; @@ -191,7 +191,7 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) if (newBufPos == -1) goto exit_with_error; - MmsValue* elementValue = MmsValue_decodeMmsData(buffer, bufPos, dataLength); + MmsValue* elementValue = MmsValue_decodeMmsData(buffer, bufPos, dataLength, NULL); if (elementValue == NULL) goto exit_with_error; @@ -209,12 +209,12 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) case 0x80: /* MMS_DATA_ACCESS_ERROR */ value = MmsValue_newDataAccessError((MmsDataAccessError) BerDecoder_decodeUint32(buffer, dataLength, bufPos)); - + bufPos += dataLength; break; case 0x83: /* MMS_BOOLEAN */ value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); - + bufPos += dataLength; break; case 0x84: /* MMS_BIT_STRING */ @@ -223,6 +223,7 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) int bitStringLength = (8 * (dataLength - 1)) - padding; value = MmsValue_newBitString(bitStringLength); memcpy(value->value.bitString.buf, buffer + bufPos + 1, dataLength - 1); + bufPos += dataLength; } break; @@ -230,12 +231,14 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) value = MmsValue_newInteger(dataLength * 8); memcpy(value->value.integer->octets, buffer + bufPos, dataLength); value->value.integer->size = dataLength; + bufPos += dataLength; break; case 0x86: /* MMS_UNSIGNED */ value = MmsValue_newUnsigned(dataLength * 8); memcpy(value->value.integer->octets, buffer + bufPos, dataLength); value->value.integer->size = dataLength; + bufPos += dataLength; break; case 0x87: /* MMS_FLOAT */ @@ -243,15 +246,18 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) value = MmsValue_newDouble(BerDecoder_decodeDouble(buffer, bufPos)); else if (dataLength == 5) value = MmsValue_newFloat(BerDecoder_decodeFloat(buffer, bufPos)); + bufPos += dataLength; break; case 0x89: /* MMS_OCTET_STRING */ value = MmsValue_newOctetString(dataLength, dataLength); memcpy(value->value.octetString.buf, buffer + bufPos, dataLength); + bufPos += dataLength; break; case 0x8a: /* MMS_VISIBLE_STRING */ value = MmsValue_newVisibleStringFromByteArray(buffer + bufPos, dataLength); + bufPos += dataLength; break; case 0x8c: /* MMS_BINARY_TIME */ @@ -263,11 +269,14 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) if ((dataLength == 4) || (dataLength == 6)) memcpy(value->value.binaryTime.buf, buffer + bufPos, dataLength); + bufPos += dataLength; + break; case 0x90: /* MMS_STRING */ value = MmsValue_newVisibleStringFromByteArray(buffer + bufPos, dataLength); value->type = MMS_STRING; + bufPos += dataLength; break; @@ -275,6 +284,7 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) if (dataLength == 8) { value = MmsValue_newUtcTime(0); MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); + bufPos += dataLength; } else goto exit_with_error; @@ -285,6 +295,9 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength) goto exit_with_error; } + if (endBufPos != NULL) + *endBufPos = bufPos; + return value; exit_with_error: diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 2840ff5..6f8f0b3 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -115,8 +115,8 @@ appendValueToResultList(MmsValue* value, LinkedList values) } static void -appendErrorToResultList(LinkedList values, uint32_t errorCode) { - MmsValue* value = MmsValue_newDataAccessError((MmsDataAccessError) errorCode); +appendErrorToResultList(LinkedList values, MmsDataAccessError errorType) { + MmsValue* value = MmsValue_newDataAccessError(errorType); MmsValue_setDeletable(value); appendValueToResultList(value, values); } @@ -246,13 +246,13 @@ alternateArrayAccess(MmsServerConnection connection, } else /* access error */ - appendErrorToResultList(values, 10 /* object-non-existant*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } else { // invalid access if (DEBUG_MMS_SERVER) printf("Invalid alternate access\n"); - appendErrorToResultList(values, 10 /* object-non-existant*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } } @@ -295,7 +295,7 @@ addNamedVariableToResultList(MmsVariableSpecification* namedVariable, MmsDomain* if (DEBUG_MMS_SERVER) printf("MMS read: value of known variable is not found. Maybe illegal access to array element!\n"); - appendErrorToResultList(values, 10 /* object-non-existant*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } else appendValueToResultList(value, values); @@ -303,7 +303,7 @@ addNamedVariableToResultList(MmsVariableSpecification* namedVariable, MmsDomain* } else - appendErrorToResultList(values, 10 /* object-non-existant*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } @@ -479,6 +479,14 @@ exit_function: return; } +/** + * \brief implements access to list of variables (multiple MMS variables) + * + * \param connection the client connection that received the request + * \param read read request information + * \param invokeId the invoke ID of the confirmed request PDU + * \param response byte buffer to encode the response + */ static void handleReadListOfVariablesRequest( MmsServerConnection connection, @@ -524,13 +532,13 @@ handleReadListOfVariablesRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: READ domain %s not found!\n", domainIdStr); - appendErrorToResultList(values, (uint32_t) DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT /* object-non-existent*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } else { MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr); if (namedVariable == NULL) - appendErrorToResultList(values, (uint32_t) DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT /* object-non-existent*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); else addNamedVariableToResultList(namedVariable, domain, nameIdStr, values, connection, alternateAccess); @@ -549,7 +557,7 @@ handleReadListOfVariablesRequest( MmsVariableSpecification* namedVariable = MmsDevice_getNamedVariable(MmsServer_getDevice(connection->server), nameIdStr); if (namedVariable == NULL) - appendErrorToResultList(values, 10 /* object-non-existent*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); else addNamedVariableToResultList(namedVariable, (MmsDomain*) MmsServer_getDevice(connection->server), nameIdStr, values, connection, alternateAccess); @@ -558,7 +566,7 @@ handleReadListOfVariablesRequest( #endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */ else { - appendErrorToResultList(values, (uint32_t) DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT /* object-non-existent*/); + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); if (DEBUG_MMS_SERVER) printf("MMS_SERVER: READ object name type not supported!\n"); } @@ -617,6 +625,14 @@ createNamedVariableListResponse(MmsServerConnection connection, MmsNamedVariable deleteValueList(values); } +/** + * \brief implements access to named variable lists (data sets) + * + * \param connection the client connection that received the request + * \param read read request information + * \param invokeId the invoke ID of the confirmed request PDU + * \param response byte buffer to encode the response + */ static void handleReadNamedVariableListRequest( MmsServerConnection connection, diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index fabc621..6764457 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -36,47 +36,98 @@ * MMS Common support functions *********************************************************************************************/ +#define MMS_REJECT_CONFIRMED_REQUEST 1 +#define MMS_REJECT_CONFIRMED_RESPONSE 2 +#define MMS_REJECT_CONFIRMED_ERROR 3 +#define MMS_REJECT_UNCONFIRMED 4 +#define MMS_REJECT_PDU_ERROR 5 +#define MMS_REJECT_CANCEL_REQUEST 6 +#define MMS_REJECT_CANCEL_RESPONSE 7 +#define MMS_REJECT_CANCEL_ERROR 8 +#define MMS_REJECT_CONCLUDE_REQUEST 9 +#define MMS_REJECT_CONCLUDE_RESPONSE 10 +#define MMS_REJECT_CONCLUDE_ERROR 11 + +#define MMS_REJECT_CONFIRMED_REQUEST_OTHER 0 +#define MMS_REJECT_CONFIRMED_REQUEST_UNRECOGNIZED_SERVICE 1 +#define MMS_REJECT_CONFIRMED_REQUEST_UNRECOGNIZED_MODIFIER 2 +#define MMS_REJECT_CONFIRMED_REQUEST_INVALID_INVOKE_ID 3 +#define MMS_REJECT_CONFIRMED_REQUEST_INVALID_ARGUMENT 4 +#define MMS_REJECT_CONFIRMED_REQUEST_INVALID_MODIFIER 5 +#define MMS_REJECT_CONFIRMED_REQUEST_MAX_SERV_OUTSTANDING_EXCEEDED 6 +#define MMS_REJECT_CONFIRMED_REQUEST_MAX_RECURSION_EXCEEDED 8 +#define MMS_REJECT_CONFIRMED_REQUEST_VALUE_OUT_OF_RANGE 9 + +#define MMS_REJECT_PDU_ERROR_UNKNOWN_PDU_TYPE 0 +#define MMS_REJECT_PDU_ERROR_INVALID_PDU 1 +#define MMS_REJECT_PDU_ERROR_ILLEGAL_ACSI_MAPPING 2 + + +static void +mmsMsg_encodeMmsRejectPdu(uint32_t* invokeId, int rejectType, int rejectReason, ByteBuffer* response) +{ + int bufPos = 0; + uint8_t* buffer = response->buffer; + + uint32_t invokeIdLength = 0; + + uint32_t rejectPduLength = 3; + + if (invokeId != NULL) { + invokeIdLength = BerEncoder_UInt32determineEncodedSize(*invokeId); + rejectPduLength += 2 + invokeIdLength; + } + + /* Encode reject PDU */ + bufPos = BerEncoder_encodeTL(0xa4, rejectPduLength, buffer, bufPos); + + if (invokeId != NULL) { + /* original invokeId */ + bufPos = BerEncoder_encodeTL(0x80, invokeIdLength, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(*invokeId, buffer, bufPos); + } + + buffer[bufPos++] = (uint8_t) (0x80 + rejectType); + buffer[bufPos++] = 0x01; + buffer[bufPos++] = (uint8_t) rejectReason; + + response->size = bufPos; +} + void mmsMsg_createMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); + int rejectType = 0; + int rejectReason = 0; - mmsPdu->present = MmsPdu_PR_rejectPDU; + switch (reason) { - if (invokeId != NULL) { - mmsPdu->choice.rejectPDU.originalInvokeID = (Unsigned32_t*) GLOBAL_CALLOC(1, sizeof(Unsigned32_t)); - asn_long2INTEGER(mmsPdu->choice.rejectPDU.originalInvokeID, *invokeId); - } + case MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE: + rejectType = MMS_REJECT_CONFIRMED_REQUEST; + rejectReason = MMS_REJECT_CONFIRMED_REQUEST_UNRECOGNIZED_SERVICE; + break; - if (reason == MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE) { - mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; - mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = - RejectPDU__rejectReason__confirmedRequestPDU_unrecognizedService; - } - else if(reason == MMS_ERROR_REJECT_UNKNOWN_PDU_TYPE) { - mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_pduError; - asn_long2INTEGER(&mmsPdu->choice.rejectPDU.rejectReason.choice.pduError, - RejectPDU__rejectReason__pduError_unknownPduType); - } - else if (reason == MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT) { - mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; - mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = - RejectPDU__rejectReason__confirmedRequestPDU_invalidArgument; - } - else if (reason == MMS_ERROR_REJECT_INVALID_PDU) { - mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_pduError; - asn_long2INTEGER(&mmsPdu->choice.rejectPDU.rejectReason.choice.pduError, - RejectPDU__rejectReason__pduError_invalidPdu); - } - else { - mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; - mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = - RejectPDU__rejectReason__confirmedRequestPDU_other; - } + case MMS_ERROR_REJECT_UNKNOWN_PDU_TYPE: + rejectType = MMS_REJECT_PDU_ERROR; + rejectReason = MMS_REJECT_PDU_ERROR_UNKNOWN_PDU_TYPE; + break; - der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); + case MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT: + rejectType = MMS_REJECT_CONFIRMED_REQUEST; + rejectReason = MMS_REJECT_CONFIRMED_REQUEST_INVALID_ARGUMENT; + break; - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + case MMS_ERROR_REJECT_INVALID_PDU: + rejectType = MMS_REJECT_PDU_ERROR; + rejectReason = MMS_REJECT_PDU_ERROR_INVALID_PDU; + break; + + default: + rejectType = MMS_REJECT_CONFIRMED_REQUEST; + rejectReason = MMS_REJECT_CONFIRMED_REQUEST_OTHER; + } + + mmsMsg_encodeMmsRejectPdu(invokeId, rejectType, rejectReason, response); } /********************************************************************************************** diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index 7014e21..b3364dc 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -1,7 +1,7 @@ /* * mms_write_service.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2017 Michael Zillgith * * This file is part of libIEC61850. * @@ -30,46 +30,54 @@ #define CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS 100 -/********************************************************************************************** - * MMS Write Service - *********************************************************************************************/ - -int +void mmsServer_createMmsWriteResponse(MmsServerConnection connection, - int invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults) + uint32_t invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults) { - //TODO remove asn1c code - MmsPdu_t* mmsPdu = mmsServer_createConfirmedResponse(invokeId); + int bufPos = 0; + uint8_t* buffer = response->buffer; - mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present = - ConfirmedServiceResponse_PR_write; + /* Determine length fields */ - WriteResponse_t* writeResponse = - &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.write); + uint32_t invokeIdLength = BerEncoder_UInt32determineEncodedSize(invokeId); - writeResponse->list.count = numberOfItems; - writeResponse->list.size = numberOfItems; - writeResponse->list.array = (struct WriteResponse__Member**) GLOBAL_CALLOC(numberOfItems, - sizeof(struct WriteResponse__Member*)); + uint32_t accessResultsLength = 0; - int i; + int i; + for (i = 0; i < numberOfItems; i++) { + if (accessResults[i] < 0) + accessResultsLength += 2; + else + accessResultsLength += 3; + } - for (i = 0; i < numberOfItems; i++) { - writeResponse->list.array[i] = (struct WriteResponse__Member*) GLOBAL_CALLOC(1, sizeof(struct WriteResponse__Member)); + uint32_t writeResponseLength = 2 + invokeIdLength + + 1 + BerEncoder_determineLengthSize(accessResultsLength) + + accessResultsLength; - if (accessResults[i] == DATA_ACCESS_ERROR_SUCCESS) - writeResponse->list.array[i]->present = WriteResponse__Member_PR_success; - else { - writeResponse->list.array[i]->present = WriteResponse__Member_PR_failure; - asn_long2INTEGER(&writeResponse->list.array[i]->choice.failure, (long) accessResults[i]); - } - } + /* Encode write response */ - der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); + bufPos = BerEncoder_encodeTL(0xa1, writeResponseLength, buffer, bufPos); - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + /* invokeId */ + bufPos = BerEncoder_encodeTL(0x02, invokeIdLength, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - return 0; + bufPos = BerEncoder_encodeTL(0xa5, accessResultsLength, buffer, bufPos); + + for (i = 0; i < numberOfItems; i++) { + if (accessResults[i] < 0) { + buffer[bufPos++] = 0x81; + buffer[bufPos++] = 0x00; + } + else { + buffer[bufPos++] = 0x80; + buffer[bufPos++] = 0x01; + buffer[bufPos++] = (uint8_t) accessResults[i]; + } + } + + response->size = bufPos; } @@ -87,6 +95,376 @@ MmsServerConnection_sendWriteResponse(MmsServerConnection self, uint32_t invokeI MmsServer_releaseTransmitBuffer(self->server); } + +typedef struct { + uint8_t type; /* 0 = vmd-specific, 1 = domain-specific, 2 = association-specific */ + uint8_t* name; + uint8_t nameLength; + uint8_t* domain; + uint8_t domainLength; +} _MmsObjectName; + +/** + * \brief Decode MMS ObjectName + * + * \return true = valid data, false = decoding error + */ +static bool +decodeObjectName(uint8_t* buffer, int bufPos, int length, int* endPos, _MmsObjectName* objName) +{ + int dataEndBufPos = bufPos + length; + + uint8_t tag = buffer[bufPos++]; + + int dataLength; + + bufPos = BerDecoder_decodeLength(buffer, &dataLength, bufPos, dataEndBufPos); + + printf(" decodeObjectName - bufPos: %i endPos: %i tag: %02x\n", bufPos, bufPos + dataLength, tag); + + if (bufPos == -1) + return false; + + switch (tag) { + case 0x80: /* VMD specific */ + objName->type = 0; + objName->name = buffer + bufPos; + objName->nameLength = dataLength; + objName->domain = NULL; + if (bufPos + dataLength > dataEndBufPos) + return false; + + bufPos += dataLength; + break; + + case 0xa1: /* domain specific */ + objName->type = 1; + { + if (buffer[bufPos++] != 0x1a) + return false; + + int nameLength; + + bufPos = BerDecoder_decodeLength(buffer, &nameLength, bufPos, dataEndBufPos); + + if (bufPos == -1) + return false; + + objName->domainLength = nameLength; + objName->domain = buffer + bufPos; + + if (bufPos + nameLength >= dataEndBufPos) + return false; + + bufPos += nameLength; + + if (buffer[bufPos++] != 0x1a) + return false; + + bufPos = BerDecoder_decodeLength(buffer, &nameLength, bufPos, dataEndBufPos); + + objName->nameLength = nameLength; + objName->name = buffer + bufPos; + + if (bufPos + nameLength > dataEndBufPos) + return false; + + bufPos += nameLength; + } + break; + + case 0x82: /* association specific */ + objName->type = 2; + objName->name = buffer + bufPos; + objName->nameLength = dataLength; + objName->domain = NULL; + if (bufPos + dataLength > dataEndBufPos) + return false; + + bufPos += dataLength; + break; + } + + if (endPos != NULL) + *endPos = bufPos; + + return true; +} + +static bool +decodeVarSpec(uint8_t* buffer, int bufPos, int length, int* endPos) +{ + int dataEndBufPos = bufPos + length; + + uint8_t tag = buffer[bufPos++]; + + printf(" varSpec - bufPos: %i endPos: %i tag: %02x\n", bufPos - 1, bufPos + length, tag); + + if (tag != 0x30) + return false; + + int dataLength; + + bufPos = BerDecoder_decodeLength(buffer, &dataLength, bufPos, dataEndBufPos); + + printf("dataLength = %i - bufPos: %i\n", dataLength, bufPos); + + if (bufPos < 0) + return false; + + tag = buffer[bufPos++]; + + printf(" varSpec - bufPos: %i endPos: %i tag: %02x\n", bufPos - 1, bufPos + length, tag); + + if (tag != 0xa0) + return false; + + bufPos = BerDecoder_decodeLength(buffer, &dataLength, bufPos, dataEndBufPos); + + if (bufPos < 0) + return false; + + printf("dataLength = %i - bufPos: %i\n", dataLength, bufPos); + + _MmsObjectName objName; + + if (decodeObjectName(buffer, bufPos, bufPos + dataLength, &bufPos, &objName) == false) + return false; + + if (objName.domain != NULL) + printf("domain name: %.*s\n", objName.domainLength, objName.domain); + if (objName.name != NULL) + printf("item name: %.*s\n", objName.nameLength, objName.name); + + if (endPos != NULL) + *endPos = bufPos; + + return true; +} + +void +mmsServer_handleWriteRequest2( + MmsServerConnection connection, + uint8_t* buffer, int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response) +{ + + bool isAccessSpecification = true; + + while (bufPos < maxBufPos) { + uint8_t tag = buffer[bufPos++]; + int length; + + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + + if (tag == 0xa1) { /* variable access specification - variable list name (data set) */ + isAccessSpecification = false; + + + } + if (tag == 0xa0) { + if (isAccessSpecification) { /* variable access specification - list of variable names */ + printf("VAR ACCESS SPEC\n"); + + isAccessSpecification = false; + + int dataBufPos = bufPos; + + while (dataBufPos < (bufPos + length)) { + + + if (decodeVarSpec(buffer, dataBufPos, length, &dataBufPos) == false) { + printf("Failed to decode MMS var access spec\n"); + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + } + } + else { + printf("LIST OF DATA\n"); + + + int dataBufPos = bufPos; + + while (dataBufPos < (bufPos + length)) { + printf("lod - dataBufPos: %i endPos: %i\n", dataBufPos, bufPos + length); + + MmsValue* newValue = MmsValue_decodeMmsData(buffer, dataBufPos, length, &dataBufPos); + + if (newValue != NULL) { + printf(" Decoded MMS data value:\n"); + + uint8_t printBuf[1024]; + + MmsValue_printToBuffer(newValue, printBuf, 1024); + + printf(" %s\n", printBuf); + } + else { + //TODO cleanup already decoded MmsValue instances + printf(" Failed to decode MMS data value\n"); + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + } + } + } + + bufPos += length; + } +} + +static void +createWriteNamedVariableListResponse( + MmsServerConnection connection, + WriteRequest_t* writeRequest, + uint32_t invokeId, + MmsNamedVariableList namedList, + ByteBuffer* response) +{ + bool sendResponse = true; + + LinkedList variables = MmsNamedVariableList_getVariableList(namedList); + + int numberOfWriteItems = LinkedList_size(variables); + + if (numberOfWriteItems > CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_OTHER, response); + return; + } + + /* write variables and send response */ + + MmsDataAccessError accessResults[CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS * sizeof(MmsDataAccessError)]; + + LinkedList element; + + int i = 0; + + for (element = LinkedList_getNext(variables); element != NULL; element = LinkedList_getNext(element)) { + MmsNamedVariableListEntry variableListEntry = (MmsNamedVariableListEntry) LinkedList_getData(element); + + MmsDomain* variableDomain = MmsNamedVariableListEntry_getDomain(variableListEntry); + char* variableName = MmsNamedVariableListEntry_getVariableName(variableListEntry); + + MmsValue* oldValue = mmsServer_getValue(connection->server, variableDomain, variableName, connection); + + Data_t* dataElement = writeRequest->listOfData.list.array[i]; + + MmsValue* newValue = mmsMsg_parseDataElement(dataElement); + + if (newValue == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + } + else if (MmsValue_equalTypes(oldValue, newValue) == false) { + MmsValue_delete(newValue); + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + } + else { + MmsDataAccessError valueIndication = + mmsServer_setValue(connection->server, variableDomain, variableName, newValue, connection); + + accessResults[i] = valueIndication; + + if (valueIndication == DATA_ACCESS_ERROR_NO_RESPONSE) + sendResponse = false; + + MmsValue_delete(newValue); + } + + i++; + } + + if (sendResponse) + mmsServer_createMmsWriteResponse(connection, invokeId, response, numberOfWriteItems, accessResults); +} + +static void +handleWriteNamedVariableListRequest( + MmsServerConnection connection, + WriteRequest_t* writeRequest, + uint32_t invokeId, + ByteBuffer* response) +{ + if (writeRequest->variableAccessSpecification.choice.variableListName.present == ObjectName_PR_domainspecific) + { + char domainIdStr[65]; + char nameIdStr[65]; + + mmsMsg_copyAsn1IdentifierToStringBuffer(writeRequest->variableAccessSpecification.choice.variableListName.choice.domainspecific.domainId, + domainIdStr, 65); + + mmsMsg_copyAsn1IdentifierToStringBuffer(writeRequest->variableAccessSpecification.choice.variableListName.choice.domainspecific.itemId, + nameIdStr, 65); + + MmsDomain* domain = MmsDevice_getDomain(MmsServer_getDevice(connection->server), domainIdStr); + + if (domain == NULL) { + if (DEBUG_MMS_SERVER) + printf("MMS write: domain %s not found!\n", domainIdStr); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + + return; + } + else { + MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); + + if (namedList != NULL) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: named variable list %s not found!\n", nameIdStr); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + } + } + + } + else if (writeRequest->variableAccessSpecification.choice.variableListName.present == ObjectName_PR_vmdspecific) { + char listName[65]; + + mmsMsg_copyAsn1IdentifierToStringBuffer(writeRequest->variableAccessSpecification.choice.variableListName.choice.vmdspecific, + listName, 65); + + MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName); + + if (namedList != NULL) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: vmd specific named variable list %s not found!\n", listName); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + } + } +#if (MMS_DYNAMIC_DATA_SETS == 1) + else if (writeRequest->variableAccessSpecification.choice.variableListName.present == ObjectName_PR_aaspecific) { + char listName[65]; + + mmsMsg_copyAsn1IdentifierToStringBuffer(writeRequest->variableAccessSpecification.choice.variableListName.choice.aaspecific, + listName, 65); + + MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName); + + if (namedList != NULL) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: association specific named variable list %s not found!\n", listName); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + } + } +#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ + else + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + +} + + void mmsServer_handleWriteRequest( MmsServerConnection connection, @@ -94,8 +472,6 @@ mmsServer_handleWriteRequest( uint32_t invokeId, ByteBuffer* response) { - WriteRequest_t* writeRequest = 0; - MmsPdu_t* mmsPdu = 0; asn_dec_rval_t rval; /* Decoder return value */ @@ -107,178 +483,206 @@ mmsServer_handleWriteRequest( return; } - writeRequest = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); + WriteRequest_t* writeRequest = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); - int numberOfWriteItems = writeRequest->variableAccessSpecification.choice.listOfVariable.list.count; - - if (numberOfWriteItems < 1) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - return; + if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) { + handleWriteNamedVariableListRequest(connection, writeRequest, invokeId, response); + goto exit_function; } + else if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) { - if (numberOfWriteItems > CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_OTHER, response); - return; - } + int numberOfWriteItems = writeRequest->variableAccessSpecification.choice.listOfVariable.list.count; - if (writeRequest->listOfData.list.count != numberOfWriteItems) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - return; - } - - MmsDataAccessError accessResults[CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS * sizeof(MmsDataAccessError)]; - - bool sendResponse = true; - - int i; - - for (i = 0; i < numberOfWriteItems; i++) { - ListOfVariableSeq_t* varSpec = - writeRequest->variableAccessSpecification.choice.listOfVariable.list.array[i]; - - if (varSpec->variableSpecification.present != VariableSpecification_PR_name) { - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; - continue; + if (numberOfWriteItems < 1) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + goto exit_function; } - MmsVariableSpecification* variable; + if (numberOfWriteItems > CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_OTHER, response); + goto exit_function; + } - MmsDevice* device = MmsServer_getDevice(connection->server); + if (writeRequest->listOfData.list.count != numberOfWriteItems) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + goto exit_function; + } - MmsDomain* domain = NULL; + MmsDataAccessError accessResults[CONFIG_MMS_WRITE_SERVICE_MAX_NUMBER_OF_WRITE_ITEMS * sizeof(MmsDataAccessError)]; - char* nameIdStr; + bool sendResponse = true; - if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) { - Identifier_t domainId = varSpec->variableSpecification.choice.name.choice.domainspecific.domainId; - char* domainIdStr = StringUtils_createStringFromBuffer(domainId.buf, domainId.size); + int i; - domain = MmsDevice_getDomain(device, domainIdStr); + for (i = 0; i < numberOfWriteItems; i++) { + ListOfVariableSeq_t* varSpec = + writeRequest->variableAccessSpecification.choice.listOfVariable.list.array[i]; - GLOBAL_FREEMEM(domainIdStr); + if (varSpec->variableSpecification.present != VariableSpecification_PR_name) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + continue; + } - if (domain == NULL) { + MmsVariableSpecification* variable; + + MmsDevice* device = MmsServer_getDevice(connection->server); + + MmsDomain* domain = NULL; + + char nameIdStr[65]; + + if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) { + Identifier_t domainId = varSpec->variableSpecification.choice.name.choice.domainspecific.domainId; + + char domainIdStr[65]; + + mmsMsg_copyAsn1IdentifierToStringBuffer(domainId, domainIdStr, 65); + + domain = MmsDevice_getDomain(device, domainIdStr); + + if (domain == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + continue; + } + + Identifier_t nameId = varSpec->variableSpecification.choice.name.choice.domainspecific.itemId; + + mmsMsg_copyAsn1IdentifierToStringBuffer(nameId, nameIdStr, 65); + + variable = MmsDomain_getNamedVariable(domain, nameIdStr); + } + + #if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) + else if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_vmdspecific) { + Identifier_t nameId = varSpec->variableSpecification.choice.name.choice.vmdspecific; + + mmsMsg_copyAsn1IdentifierToStringBuffer(nameId, nameIdStr, 65); + + variable = MmsDevice_getNamedVariable(device, nameIdStr); + } + #endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */ + + else { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + continue; + } + + if (variable == NULL) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; continue; } - Identifier_t nameId = varSpec->variableSpecification.choice.name.choice.domainspecific.itemId; - nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size); + AlternateAccess_t* alternateAccess = varSpec->alternateAccess; - variable = MmsDomain_getNamedVariable(domain, nameIdStr); - } + if (alternateAccess != NULL) { + if (variable->type != MMS_ARRAY) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + continue; + } -#if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) - else if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_vmdspecific) { + if (!mmsServer_isIndexAccess(alternateAccess)) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + continue; + } + } - Identifier_t nameId = varSpec->variableSpecification.choice.name.choice.vmdspecific; - nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size); + Data_t* dataElement = writeRequest->listOfData.list.array[i]; - variable = MmsDevice_getNamedVariable(device, nameIdStr); - } -#endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */ + MmsValue* value = mmsMsg_parseDataElement(dataElement); - else { - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; - continue; - } - - if (variable == NULL) { - GLOBAL_FREEMEM(nameIdStr); - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; - continue; - } - - AlternateAccess_t* alternateAccess = varSpec->alternateAccess; - - if (alternateAccess != NULL) { - if (variable->type != MMS_ARRAY) { - GLOBAL_FREEMEM(nameIdStr); + if (value == NULL) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } - if (!mmsServer_isIndexAccess(alternateAccess)) { - GLOBAL_FREEMEM(nameIdStr); - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; - continue; - } - } + if (alternateAccess != NULL) { - Data_t* dataElement = writeRequest->listOfData.list.array[i]; + if (domain == NULL) + domain = (MmsDomain*) device; - MmsValue* value = mmsMsg_parseDataElement(dataElement); + MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); - if (value == NULL) { - GLOBAL_FREEMEM(nameIdStr); - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; - continue; - } + if (cachedArray == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + goto end_of_main_loop; + } - /* Check for correct type */ - if (MmsValue_getType(value) != MmsVariableSpecification_getType(variable)) { - GLOBAL_FREEMEM(nameIdStr); - MmsValue_delete(value); - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - continue; - } + int index = mmsServer_getLowIndex(alternateAccess); + int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - if (alternateAccess != NULL) { + if (numberOfElements == 0) { /* select single array element with index */ - if (domain != NULL) - domain = (MmsDomain*) device; + MmsValue* elementValue = MmsValue_getElement(cachedArray, index); - MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); + if (elementValue == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + goto end_of_main_loop; + } + + if (MmsValue_update(elementValue, value) == false) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + } + else { /* select sub-array with start-index and number-of-elements */ + + if (MmsValue_getType(value) != MMS_ARRAY) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + + int elementNo; + + for (elementNo = 0; elementNo < numberOfElements; elementNo++) { + MmsValue* newElement = MmsValue_getElement(value, elementNo); + MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); + + if ((elementValue == NULL) || (newElement == NULL) ) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + + if (MmsValue_update(elementValue, newElement) == false) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + + } + } + + accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; + goto end_of_main_loop; - if (cachedArray == NULL) { - GLOBAL_FREEMEM(nameIdStr); - MmsValue_delete(value); - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; - continue; } - int index = mmsServer_getLowIndex(alternateAccess); - - MmsValue* elementValue = MmsValue_getElement(cachedArray, index); - - if (elementValue == NULL) { - GLOBAL_FREEMEM(nameIdStr); - MmsValue_delete(value); - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; - continue; - } - - if (MmsValue_update(elementValue, value) == false) { - GLOBAL_FREEMEM(nameIdStr); - MmsValue_delete(value); + /* Check for correct type */ + if (MmsValue_getType(value) != MmsVariableSpecification_getType(variable)) { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - continue; + goto end_of_main_loop; } - GLOBAL_FREEMEM(nameIdStr); - MmsValue_delete(value); - accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; - continue; + MmsDataAccessError valueIndication = + mmsServer_setValue(connection->server, domain, nameIdStr, value, connection); + if (valueIndication == DATA_ACCESS_ERROR_NO_RESPONSE) + sendResponse = false; + + accessResults[i] = valueIndication; + +end_of_main_loop: + + MmsValue_delete(value); } - MmsDataAccessError valueIndication = - mmsServer_setValue(connection->server, domain, nameIdStr, value, connection); - - if (valueIndication == DATA_ACCESS_ERROR_NO_RESPONSE) - sendResponse = false; - - accessResults[i] = valueIndication; - - MmsValue_delete(value); - - GLOBAL_FREEMEM(nameIdStr); - } - - if (sendResponse) { - mmsServer_createMmsWriteResponse(connection, invokeId, response, numberOfWriteItems, accessResults); + if (sendResponse) + mmsServer_createMmsWriteResponse(connection, invokeId, response, numberOfWriteItems, accessResults); + } + else { /* unknown request type */ + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + goto exit_function; } +exit_function: asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 3f5333e..5ebaadb 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -67,6 +67,7 @@ struct sSVClientASDU { uint8_t* smpCnt; uint8_t* confRev; + uint8_t* refrTm; uint8_t* smpSynch; @@ -232,6 +233,10 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) asdu.confRev = buffer + bufPos; break; + case 0x84: + asdu.refrTm = buffer + bufPos; + break; + case 0x85: asdu.smpSynch = buffer + bufPos; break; @@ -491,6 +496,52 @@ SVClientASDU_getSmpCnt(SVClientASDU self) return retVal; } +static uint64_t +decodeUtcTime(uint8_t* buffer, uint8_t* timeQuality) +{ + uint32_t timeval32; + + timeval32 = buffer[3]; + timeval32 += buffer[2] * 0x100; + timeval32 += buffer[1] * 0x10000; + timeval32 += buffer[0] * 0x1000000; + + uint32_t msVal; + + uint32_t fractionOfSecond; + + fractionOfSecond = buffer[6]; + fractionOfSecond += buffer[5] * 0x100; + fractionOfSecond += buffer[4] * 0x10000; + + msVal = (uint32_t) (((uint64_t) fractionOfSecond * 1000) / 16777215); + + if (timeQuality != NULL) + *timeQuality = buffer[7]; + + uint64_t timeval64 = (uint64_t) timeval32 * 1000 + (uint64_t) msVal; + + return timeval64; +} + +uint64_t +SVClientASDU_getRefrTmAsMs(SVClientASDU self) +{ + uint64_t msTime = 0; + + if (self->refrTm != NULL) + msTime = decodeUtcTime(self->refrTm, NULL); + + return msTime; +} + +bool +SVClientASDU_hasRefrTm(SVClientASDU self) +{ + return (self->refrTm != NULL); +} + + const char* SVClientASDU_getSvId(SVClientASDU self) { diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index c463e5b..eefcbaa 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -268,6 +268,26 @@ SVClientASDU_getSvId(SVClientASDU self); uint32_t SVClientASDU_getConfRev(SVClientASDU self); +/** + * \brief Check if RefrTm value is included in the SV ASDU + * + * \param self ASDU object instance + * + * \return true if RefrTm value is present, false otherwise + */ +bool +SVClientASDU_hasRefrTm(SVClientASDU self); + +/** + * \brief Get the RefrTim value included in SV ASDU as ms timestamp + * + * \param self ASDU object instance + * + * \return the time value as ms timestamp or 0 if RefrTm is not present in the SV ASDU + */ +uint64_t +SVClientASDU_getRefrTmAsMs(SVClientASDU self); + /** * \brief Get an INT8 data value in the data part of the ASDU * diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index c3a2892..228dab8 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -569,4 +569,5 @@ EXPORTS IedConnection_setFilestoreBasepath IedServer_setFilestoreBasepath IedModel_getDeviceByInst - + MmsConnection_writeNamedVariableList + IedConnection_writeDataSetValues \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 52e6cbf..87146bd 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -648,4 +648,7 @@ EXPORTS IedServer_setFilestoreBasepath GooseReceiver_isRunning IedModel_getDeviceByInst - \ No newline at end of file + MmsConnection_writeNamedVariableList + SVClientASDU_hasRefrTm + SVClientASDU_getRefrTmAsMs + IedConnection_writeDataSetValues