From b4a1f7c918c1620c63e3af6454be4c9dd13c9207 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 10 Aug 2024 23:32:59 +0200 Subject: [PATCH 001/158] feat(core): Add namespace mapping table and methods for conversion --- include/open62541/types.h | 60 +++++++++++++++++++++++++++++++++++++++ src/ua_types.c | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/include/open62541/types.h b/include/open62541/types.h index 603642c11..609c8a937 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1245,6 +1245,66 @@ UA_INLINABLE(UA_Boolean return (UA_order(p1, p2, type) == UA_ORDER_EQ); }) +/** + * Namespace Mapping + * ----------------- + * + * Every :ref:`nodeid` references a namespace index. Actually the namespace is + * identified by its URI. The namespace-array of the server maps the URI to the + * namespace index in the array. Namespace zero always has the URI + * ```http://opcfoundation.org/UA/```. Namespace one has the application URI of + * the server. All namespaces beyond get a custom assignment. + * + * In order to have predictable NodeIds, a client might predefined its own + * namespace array that is different from the server's. When a NodeId is decoded + * from a network message (binary or JSON), a mapping-table can be used to + * automatically translate between the remote and local namespace index. The + * mapping is typically done by the client who can generate the mapping table + * after reading the namespace-array of the server. The reverse mapping is done + * in the encoding if the mapping table is set in the options. + * + * The mapping table also contains the full URI names. It is also used to + * translate the ``NamespaceUri`` field of an ExpandedNodeId into the namespace + * index of the NodeId embedded in the ExpandedNodeId. */ + +typedef struct { + /* Namespaces with their local index */ + UA_String *namespaceUris; + size_t namespaceUrisSize; + + /* Map from local to remote indices */ + UA_UInt16 *local2remote; + size_t local2remoteSize; + + /* Map from remote to local indices */ + UA_UInt16 *remote2local; + size_t remote2localSize; +} UA_NamespaceMapping; + +/* If the index is unknown, returns (UINT16_MAX - index) */ +UA_UInt16 +UA_NamespaceMapping_local2Remote(UA_NamespaceMapping *nm, + UA_UInt16 localIndex); + +UA_UInt16 +UA_NamespaceMapping_remote2Local(UA_NamespaceMapping *nm, + UA_UInt16 remoteIndex); + +/* Returns an error if the namespace uri was not found. + * The pointer to the index argument needs to be non-NULL. */ +UA_StatusCode +UA_NamespaceMapping_uri2Index(UA_NamespaceMapping *nm, + UA_String uri, UA_UInt16 *index); + +/* Upon success, the uri string gets set. The string is not copied and must not + * outlive the namespace mapping structure. */ +UA_StatusCode +UA_NamespaceMapping_index2Uri(UA_NamespaceMapping *nm, + UA_UInt16 index, UA_String *uri); + +void +UA_NamespaceMapping_delete(UA_NamespaceMapping *nm); + /** * Binary Encoding/Decoding * ------------------------ diff --git a/src/ua_types.c b/src/ua_types.c index e4391d6b3..ad177ba4b 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -2280,3 +2280,52 @@ UA_NumericRange_parse(UA_NumericRange *range, const UA_String str) { return retval; } + +/*********************/ +/* Namespace Mapping */ +/*********************/ + +UA_UInt16 +UA_NamespaceMapping_local2Remote(UA_NamespaceMapping *nm, UA_UInt16 localIndex) { + if(localIndex >= nm->local2remoteSize) + return UA_UINT16_MAX - localIndex; + return nm->local2remote[localIndex]; +} + +UA_UInt16 +UA_NamespaceMapping_remote2Local(UA_NamespaceMapping *nm, UA_UInt16 remoteIndex) { + if(remoteIndex >= nm->remote2localSize) + return UA_UINT16_MAX - remoteIndex; + return nm->remote2local[remoteIndex]; +} + +/* Returns an error if the uri was not found. + * The pointer to the index argument needs to be non-NULL. */ +UA_StatusCode +UA_NamespaceMapping_uri2Index(UA_NamespaceMapping *nm, UA_String uri, UA_UInt16 *index) { + for(size_t i = 0; i < nm->namespaceUrisSize; i++) { + if(UA_String_equal(&uri, &nm->namespaceUris[i])) { + *index = (UA_UInt16)i; + return UA_STATUSCODE_GOOD; + } + } + return UA_STATUSCODE_BADNOTFOUND; +} + +UA_StatusCode +UA_NamespaceMapping_index2Uri(UA_NamespaceMapping *nm, UA_UInt16 index, UA_String *uri) { + if(nm->namespaceUrisSize <= index) + return UA_STATUSCODE_BADNOTFOUND; + *uri = nm->namespaceUris[index]; + return UA_STATUSCODE_GOOD; +} + +void +UA_NamespaceMapping_delete(UA_NamespaceMapping *nm) { + if(!nm) + return; + UA_Array_delete(nm->namespaceUris, nm->namespaceUrisSize, &UA_TYPES[UA_TYPES_STRING]); + UA_Array_delete(nm->local2remote, nm->local2remoteSize, &UA_TYPES[UA_TYPES_UINT16]); + UA_Array_delete(nm->remote2local, nm->remote2localSize, &UA_TYPES[UA_TYPES_UINT16]); + UA_free(nm); +} From 28554978c1d2cd90954dfcb20e221ab58a24da84 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 12 Aug 2024 21:27:34 +0200 Subject: [PATCH 002/158] feat(core): Extend the binary encoding to take encoding options --- include/open62541/types.h | 15 +++++- src/client/ua_client_connect.c | 4 +- src/pubsub/ua_pubsub_networkmessage_binary.c | 20 ++++---- src/pubsub/ua_pubsub_writer.c | 8 ++-- src/server/ua_server_binary.c | 8 ++-- src/server/ua_server_ns0_gds.c | 4 +- src/ua_securechannel.c | 19 +++++--- src/ua_securechannel.h | 4 ++ src/ua_securechannel_crypto.c | 10 ++-- src/ua_types_encoding_binary.c | 20 +++++--- src/ua_types_encoding_binary.h | 1 + src/util/ua_util_internal.h | 4 +- tests/check_chunking.c | 16 +++---- tests/check_types_builtin.c | 24 +++++----- tests/check_types_custom.c | 46 +++++++++---------- tests/check_types_memory.c | 10 ++-- tests/encryption/check_gds_informationmodel.c | 2 +- tests/fuzz/fuzz_binary_decode.cc | 4 +- .../attic/check_pubsub_connection_xdp.c | 4 +- .../pubsub/check_pubsub_connection_ethernet.c | 4 +- tests/pubsub/check_pubsub_connection_mqtt.c | 4 +- tests/pubsub/check_pubsub_connection_udp.c | 4 +- tests/server/check_server_readspeed.c | 4 +- tools/ua2json/ua2json.c | 2 +- 24 files changed, 137 insertions(+), 104 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 609c8a937..818d27784 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1312,10 +1312,17 @@ UA_NamespaceMapping_delete(UA_NamespaceMapping *nm); * Encoding and decoding routines for the binary format. For the binary decoding * additional data types can be forwarded. */ +typedef struct { + /* Mapping of namespace indices in NodeIds and of NamespaceUris in + * ExpandedNodeIds. */ + UA_NamespaceMapping *namespaceMapping; +} UA_EncodeBinaryOptions; + /* Returns the number of bytes the value p takes in binary encoding. Returns * zero if an error occurs. */ UA_EXPORT size_t -UA_calcSizeBinary(const void *p, const UA_DataType *type); +UA_calcSizeBinary(const void *p, const UA_DataType *type, + UA_EncodeBinaryOptions *options); /* Encodes a data-structure in the binary format. If outBuf has a length of * zero, a buffer of the required size is allocated. Otherwise, encoding into @@ -1323,7 +1330,7 @@ UA_calcSizeBinary(const void *p, const UA_DataType *type); * small). */ UA_EXPORT UA_StatusCode UA_encodeBinary(const void *p, const UA_DataType *type, - UA_ByteString *outBuf); + UA_ByteString *outBuf, UA_EncodeBinaryOptions *options); /* The structure with the decoding options may be extended in the future. * Zero-out the entire structure initially to ensure code-compatibility when @@ -1332,6 +1339,10 @@ typedef struct { /* Begin of a linked list with custom datatype definitions */ const UA_DataTypeArray *customTypes; + /* Mapping of namespace indices in NodeIds and of NamespaceUris in + * ExpandedNodeIds. */ + UA_NamespaceMapping *namespaceMapping; + /* Override calloc for arena-based memory allocation. Note that allocated * memory is not freed if decoding fails afterwards. */ void *callocContext; diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index 6922c71dd..de40c6993 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -431,7 +431,7 @@ sendHELMessage(UA_Client *client) { const UA_Byte *bufEnd = &message.data[message.length]; client->connectStatus = UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); /* Encode the message header at offset 0 */ UA_TcpMessageHeader messageHeader; @@ -440,7 +440,7 @@ sendHELMessage(UA_Client *client) { bufPos = message.data; retval = UA_encodeBinaryInternal(&messageHeader, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); if(retval != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, client->channel.connectionId, &message); return retval; diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index 7ffa74508..cfefc748e 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -85,12 +85,12 @@ UA_NetworkMessage_updateBufferedMessage(UA_NetworkMessageOffsetBuffer *buffer) { case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: rv = UA_encodeBinaryInternal(nmo->content.value.value.data, nmo->content.value.value.type, - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); break; case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: rv = UA_encodeBinaryInternal((*nmo->content.externalValue)->value.data, (*nmo->content.externalValue)->value.type, - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); break; default: break; /* The other fields are assumed to not change between messages. @@ -1404,13 +1404,15 @@ UA_DataSetMessage_keyFrame_encodeBinary(const UA_DataSetMessage* src, UA_Byte ** if(fmd->maxStringLength != 0 && (v->value.type->typeKind == UA_DATATYPEKIND_STRING || v->value.type->typeKind == UA_DATATYPEKIND_BYTESTRING)) { - rv = UA_encodeBinaryInternal(valuePtr, v->value.type, bufPos, &bufEnd, NULL, NULL); + rv = UA_encodeBinaryInternal(valuePtr, v->value.type, bufPos, &bufEnd, + NULL, NULL, NULL); size_t lengthDifference = fmd->maxStringLength - ((UA_String *)valuePtr)->length; memset(*bufPos, 0, lengthDifference); *bufPos += lengthDifference; } else { /* Padding not yet supported for strings as part of structures */ - rv = UA_encodeBinaryInternal(valuePtr, v->value.type, bufPos, &bufEnd, NULL, NULL); + rv = UA_encodeBinaryInternal(valuePtr, v->value.type, bufPos, &bufEnd, + NULL, NULL, NULL); } valuePtr += v->value.type->memSize; } @@ -1684,7 +1686,7 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, if(p->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { if(offsetBuffer) nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT; - size += UA_calcSizeBinary(&v->value, &UA_TYPES[UA_TYPES_VARIANT]); + size += UA_calcSizeBinary(&v->value, &UA_TYPES[UA_TYPES_VARIANT], NULL); } else if(p->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) { if(p->data.keyFrameData.dataSetFields != NULL) { if(offsetBuffer) { @@ -1708,7 +1710,7 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, for(size_t cnt = 0; cnt < fmd->arrayDimensionsSize; cnt++) { elemCnt *= fmd->arrayDimensions[cnt]; } - size += (elemCnt * UA_calcSizeBinary(v->value.data, v->value.type)); + size += (elemCnt * UA_calcSizeBinary(v->value.data, v->value.type, NULL)); /* Handle zero-padding for strings with max-string-length. * Currently not supported for strings that are a part of larger @@ -1735,7 +1737,7 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, } else if(p->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) { if(offsetBuffer) nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE; - size += UA_calcSizeBinary(v, &UA_TYPES[UA_TYPES_DATAVALUE]); + size += UA_calcSizeBinary(v, &UA_TYPES[UA_TYPES_DATAVALUE], NULL); } } } else if(p->header.dataSetMessageType == UA_DATASETMESSAGE_DATADELTAFRAME) { @@ -1751,9 +1753,9 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, for(UA_UInt16 i = 0; i < p->data.deltaFrameData.fieldCount; i++) { const UA_DataValue *v = &p->data.deltaFrameData.deltaFrameFields[i].fieldValue; if(p->header.fieldEncoding == UA_FIELDENCODING_VARIANT) - size += UA_calcSizeBinary(&v->value, &UA_TYPES[UA_TYPES_VARIANT]); + size += UA_calcSizeBinary(&v->value, &UA_TYPES[UA_TYPES_VARIANT], NULL); else if(p->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) - size += UA_calcSizeBinary(v, &UA_TYPES[UA_TYPES_DATAVALUE]); + size += UA_calcSizeBinary(v, &UA_TYPES[UA_TYPES_DATAVALUE], NULL); } } else { return 0; diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index 3efad271e..d17ad2df0 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -407,8 +407,8 @@ valueChangedVariant(UA_Variant *oldValue, UA_Variant *newValue) { if(!oldValue || !newValue) return false; - size_t oldValueEncodingSize = UA_calcSizeBinary(oldValue, &UA_TYPES[UA_TYPES_VARIANT]); - size_t newValueEncodingSize = UA_calcSizeBinary(newValue, &UA_TYPES[UA_TYPES_VARIANT]); + size_t oldValueEncodingSize = UA_calcSizeBinary(oldValue, &UA_TYPES[UA_TYPES_VARIANT], NULL); + size_t newValueEncodingSize = UA_calcSizeBinary(newValue, &UA_TYPES[UA_TYPES_VARIANT], NULL); if(oldValueEncodingSize == 0 || newValueEncodingSize == 0) return false; @@ -435,12 +435,12 @@ valueChangedVariant(UA_Variant *oldValue, UA_Variant *newValue) { UA_Boolean compareResult = false; /* default */ res = UA_encodeBinaryInternal(oldValue, &UA_TYPES[UA_TYPES_VARIANT], - &bufPosOldValue, &bufEndOldValue, NULL, NULL); + &bufPosOldValue, &bufEndOldValue, NULL, NULL, NULL); if(res != UA_STATUSCODE_GOOD) goto cleanup; res = UA_encodeBinaryInternal(newValue, &UA_TYPES[UA_TYPES_VARIANT], - &bufPosNewValue, &bufEndNewValue, NULL, NULL); + &bufPosNewValue, &bufEndNewValue, NULL, NULL, NULL); if(res != UA_STATUSCODE_GOOD) goto cleanup; diff --git a/src/server/ua_server_binary.c b/src/server/ua_server_binary.c index 9e2f54b55..2a355e640 100644 --- a/src/server/ua_server_binary.c +++ b/src/server/ua_server_binary.c @@ -258,10 +258,10 @@ processHEL(UA_Server *server, UA_SecureChannel *channel, const UA_ByteString *ms const UA_Byte *bufEnd = &ack_msg.data[ack_msg.length]; retval |= UA_encodeBinaryInternal(&ackHeader, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); retval |= UA_encodeBinaryInternal(&ackMessage, &UA_TRANSPORT[UA_TRANSPORT_TCPACKNOWLEDGEMESSAGE], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); if(retval != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, channel->connectionId, &ack_msg); return retval; @@ -959,7 +959,7 @@ sendRHEMessage(UA_Server *server, uintptr_t connectionId, UA_StatusCode result = UA_encodeBinaryInternal(&reverseHello, &UA_TRANSPORT[UA_TRANSPORT_TCPREVERSEHELLOMESSAGE], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); if(result != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, connectionId, &message); @@ -973,7 +973,7 @@ sendRHEMessage(UA_Server *server, uintptr_t connectionId, bufPos = message.data; retval = UA_encodeBinaryInternal(&messageHeader, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); if(retval != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, connectionId, &message); return retval; diff --git a/src/server/ua_server_ns0_gds.c b/src/server/ua_server_ns0_gds.c index 7f69d746a..54c30ff99 100644 --- a/src/server/ua_server_ns0_gds.c +++ b/src/server/ua_server_ns0_gds.c @@ -670,7 +670,7 @@ openTrustList(UA_Server *server, certGroup->getTrustList(certGroup, &trustList); UA_ByteString encTrustList = UA_BYTESTRING_NULL; - retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList); + retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList, NULL); UA_TrustListDataType_clear(&trustList); if(retval != UA_STATUSCODE_GOOD) { UA_ByteString_clear(&encTrustList); @@ -737,7 +737,7 @@ openTrustListWithMask(UA_Server *server, certGroup->getTrustList(certGroup, &trustList); UA_ByteString encTrustList = UA_BYTESTRING_NULL; - retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList); + retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList, NULL); UA_TrustListDataType_clear(&trustList); if(retval != UA_STATUSCODE_GOOD) { UA_ByteString_clear(&encTrustList); diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index eabb95ea9..378174eb1 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -113,10 +113,10 @@ UA_SecureChannel_sendError(UA_SecureChannel *channel, UA_TcpErrorMessage *error) const UA_Byte *bufEnd = &msg.data[msg.length]; retval |= UA_encodeBinaryInternal(&header, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); retval |= UA_encodeBinaryInternal(error, &UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE], - &bufPos, &bufEnd, NULL, NULL); + &bufPos, &bufEnd, NULL, NULL, NULL); (void)retval; /* Encoding of these cannot fail */ msg.length = header.messageSize; cm->sendWithConnection(cm, channel->connectionId, &UA_KEYVALUEMAP_NULL, &msg); @@ -273,8 +273,12 @@ UA_SecureChannel_sendAsymmetricOPNMessage(UA_SecureChannel *channel, size_t securityHeaderLength, pre_sig_length, total_length, encryptedLength; /* Encode the message type and content */ + UA_EncodeBinaryOptions encOpts; + memset(&encOpts, 0, sizeof(UA_EncodeBinaryOptions)); + encOpts.namespaceMapping = channel->namespaceMapping; res |= UA_NodeId_encodeBinary(&contentType->binaryEncodingId, &buf_pos, buf_end); - res |= UA_encodeBinaryInternal(content, contentType, &buf_pos, &buf_end, NULL, NULL); + res |= UA_encodeBinaryInternal(content, contentType, &buf_pos, &buf_end, + &encOpts, NULL, NULL); UA_CHECK_STATUS(res, goto error); /* Compute the header length */ @@ -355,13 +359,13 @@ encodeHeadersSym(UA_MessageContext *mc, size_t totalLength) { UA_StatusCode res = UA_STATUSCODE_GOOD; res |= UA_encodeBinaryInternal(&header, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &header_pos, &mc->buf_end, NULL, NULL); + &header_pos, &mc->buf_end, NULL, NULL, NULL); res |= UA_UInt32_encodeBinary(&channel->securityToken.channelId, &header_pos, mc->buf_end); res |= UA_UInt32_encodeBinary(&channel->securityToken.tokenId, &header_pos, mc->buf_end); res |= UA_encodeBinaryInternal(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], - &header_pos, &mc->buf_end, NULL, NULL); + &header_pos, &mc->buf_end, NULL, NULL, NULL); return res; } @@ -504,9 +508,12 @@ UA_MessageContext_begin(UA_MessageContext *mc, UA_SecureChannel *channel, UA_StatusCode UA_MessageContext_encode(UA_MessageContext *mc, const void *content, const UA_DataType *contentType) { + UA_EncodeBinaryOptions encOpts; + memset(&encOpts, 0, sizeof(UA_EncodeBinaryOptions)); + encOpts.namespaceMapping = mc->channel->namespaceMapping; UA_StatusCode res = UA_encodeBinaryInternal(content, contentType, &mc->buf_pos, &mc->buf_end, - sendSymmetricEncodingCallback, mc); + &encOpts, sendSymmetricEncodingCallback, mc); if(res != UA_STATUSCODE_GOOD && mc->messageBuffer.length > 0) UA_MessageContext_abort(mc); return res; diff --git a/src/ua_securechannel.h b/src/ua_securechannel.h index 90be0885b..18d0cc88b 100644 --- a/src/ua_securechannel.h +++ b/src/ua_securechannel.h @@ -104,6 +104,10 @@ struct UA_SecureChannel { UA_ConnectionManager *connectionManager; uintptr_t connectionId; + /* The namespace mapping translates namespace indices of NodeIds during + * de/encoding (client only) */ + UA_NamespaceMapping *namespaceMapping; + /* Linked lists (only used in the server) */ TAILQ_ENTRY(UA_SecureChannel) serverEntry; TAILQ_ENTRY(UA_SecureChannel) componentEntry; diff --git a/src/ua_securechannel_crypto.c b/src/ua_securechannel_crypto.c index cc5e135fc..5dd009d0f 100644 --- a/src/ua_securechannel_crypto.c +++ b/src/ua_securechannel_crypto.c @@ -174,7 +174,7 @@ prependHeadersAsym(UA_SecureChannel *const channel, UA_Byte *header_pos, UA_StatusCode retval = UA_STATUSCODE_GOOD; retval |= UA_encodeBinaryInternal(&messageHeader, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], - &header_pos, &buf_end, NULL, NULL); + &header_pos, &buf_end, NULL, NULL, NULL); retval |= UA_UInt32_encodeBinary(&secureChannelId, &header_pos, buf_end); UA_CHECK_STATUS(retval, return retval); @@ -187,9 +187,9 @@ prependHeadersAsym(UA_SecureChannel *const channel, UA_Byte *header_pos, asymHeader.receiverCertificateThumbprint.length = 20; asymHeader.receiverCertificateThumbprint.data = channel->remoteCertificateThumbprint; } - retval = UA_encodeBinaryInternal(&asymHeader, - &UA_TRANSPORT[UA_TRANSPORT_ASYMMETRICALGORITHMSECURITYHEADER], - &header_pos, &buf_end, NULL, NULL); + retval = UA_encodeBinaryInternal( + &asymHeader, &UA_TRANSPORT[UA_TRANSPORT_ASYMMETRICALGORITHMSECURITYHEADER], + &header_pos, &buf_end, NULL, NULL, NULL); UA_CHECK_STATUS(retval, return retval); /* Increase the sequence number in the channel */ @@ -199,7 +199,7 @@ prependHeadersAsym(UA_SecureChannel *const channel, UA_Byte *header_pos, seqHeader.requestId = requestId; seqHeader.sequenceNumber = channel->sendSequenceNumber; retval = UA_encodeBinaryInternal(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], - &header_pos, &buf_end, NULL, NULL); + &header_pos, &buf_end, NULL, NULL, NULL); return retval; } diff --git a/src/ua_types_encoding_binary.c b/src/ua_types_encoding_binary.c index fea19770d..0a0e60ede 100644 --- a/src/ua_types_encoding_binary.c +++ b/src/ua_types_encoding_binary.c @@ -892,7 +892,10 @@ FUNC_ENCODE_BINARY(ExtensionObject) { * calcSizeBinary mode. This is avoids recursive cycles.*/ i32 signed_len = 0; if(ctx->end != NULL) { - size_t len = UA_calcSizeBinary(src->content.decoded.data, contentType); + UA_EncodeBinaryOptions opts; + memset(&opts, 0, sizeof(UA_EncodeBinaryOptions)); + opts.namespaceMapping = ctx->opts.namespaceMapping; + size_t len = UA_calcSizeBinary(src->content.decoded.data, contentType, &opts); UA_CHECK(len <= UA_INT32_MAX, return UA_STATUSCODE_BADENCODINGERROR); signed_len = (i32)len; } @@ -1643,6 +1646,7 @@ const encodeBinarySignature encodeBinaryJumpTable[UA_DATATYPEKINDS] = { status UA_encodeBinaryInternal(const void *src, const UA_DataType *type, u8 **bufPos, const u8 **bufEnd, + UA_EncodeBinaryOptions *options, UA_exchangeEncodeBuffer exchangeCallback, void *exchangeHandle) { if(!type || !src) @@ -1655,6 +1659,9 @@ UA_encodeBinaryInternal(const void *src, const UA_DataType *type, ctx.depth = 0; ctx.exchangeBufferCallback = exchangeCallback; ctx.exchangeBufferCallbackHandle = exchangeHandle; + memset(&ctx.opts, 0, sizeof(UA_DecodeBinaryOptions)); + if(options) + ctx.opts.namespaceMapping = options->namespaceMapping; /* Encode */ status ret = encodeWithExchangeBuffer(&ctx, src, type); @@ -1669,12 +1676,12 @@ UA_encodeBinaryInternal(const void *src, const UA_DataType *type, UA_StatusCode UA_encodeBinary(const void *p, const UA_DataType *type, - UA_ByteString *outBuf) { + UA_ByteString *outBuf, UA_EncodeBinaryOptions *options) { /* Allocate buffer */ UA_Boolean allocated = false; status res = UA_STATUSCODE_GOOD; if(outBuf->length == 0) { - size_t len = UA_calcSizeBinary(p, type); + size_t len = UA_calcSizeBinary(p, type, options); res = UA_ByteString_allocBuffer(outBuf, len); if(res != UA_STATUSCODE_GOOD) return res; @@ -1684,7 +1691,7 @@ UA_encodeBinary(const void *p, const UA_DataType *type, /* Encode */ u8 *pos = outBuf->data; const u8 *posEnd = &outBuf->data[outBuf->length]; - res = UA_encodeBinaryInternal(p, type, &pos, &posEnd, NULL, NULL); + res = UA_encodeBinaryInternal(p, type, &pos, &posEnd, options, NULL, NULL); /* Clean up */ if(res == UA_STATUSCODE_GOOD) { @@ -1904,10 +1911,11 @@ UA_decodeBinary(const UA_ByteString *inBuf, * throw an error when the buffer limits are exceeded. */ size_t -UA_calcSizeBinary(const void *p, const UA_DataType *type) { +UA_calcSizeBinary(const void *p, const UA_DataType *type, + UA_EncodeBinaryOptions *options) { u8 *pos = NULL; const u8 *posEnd = NULL; - UA_StatusCode res = UA_encodeBinaryInternal(p, type, &pos, &posEnd, NULL, NULL); + UA_StatusCode res = UA_encodeBinaryInternal(p, type, &pos, &posEnd, options, NULL, NULL); if(res != UA_STATUSCODE_GOOD) return 0; return (size_t)(uintptr_t)pos; diff --git a/src/ua_types_encoding_binary.h b/src/ua_types_encoding_binary.h index d7ab23f5b..3d4dc7130 100644 --- a/src/ua_types_encoding_binary.h +++ b/src/ua_types_encoding_binary.h @@ -77,6 +77,7 @@ extern const decodeBinarySignature decodeBinaryJumpTable[UA_DATATYPEKINDS]; UA_StatusCode UA_encodeBinaryInternal(const void *src, const UA_DataType *type, UA_Byte **bufPos, const UA_Byte **bufEnd, + UA_EncodeBinaryOptions *options, UA_exchangeEncodeBuffer exchangeCallback, void *exchangeHandle) UA_FUNC_ATTR_WARN_UNUSED_RESULT; diff --git a/src/util/ua_util_internal.h b/src/util/ua_util_internal.h index a7d819e4a..e0083aefa 100644 --- a/src/util/ua_util_internal.h +++ b/src/util/ua_util_internal.h @@ -330,12 +330,12 @@ void UA_Guid_to_hex(const UA_Guid *guid, u8* out, UA_Boolean lower); #define UA_ENCODING_HELPERS(TYPE, UPCASE_TYPE) \ static UA_INLINE size_t \ UA_##TYPE##_calcSizeBinary(const UA_##TYPE *src) { \ - return UA_calcSizeBinary(src, &UA_TYPES[UA_TYPES_##UPCASE_TYPE]); \ + return UA_calcSizeBinary(src, &UA_TYPES[UA_TYPES_##UPCASE_TYPE], NULL); \ } \ static UA_INLINE UA_StatusCode \ UA_##TYPE##_encodeBinary(const UA_##TYPE *src, UA_Byte **bufPos, const UA_Byte *bufEnd) { \ return UA_encodeBinaryInternal(src, &UA_TYPES[UA_TYPES_##UPCASE_TYPE], \ - bufPos, &bufEnd, NULL, NULL); \ + bufPos, &bufEnd, NULL, NULL, NULL); \ } \ static UA_INLINE UA_StatusCode \ UA_##TYPE##_decodeBinary(const UA_ByteString *src, size_t *offset, UA_##TYPE *dst) { \ diff --git a/tests/check_chunking.c b/tests/check_chunking.c index df757b98d..55fd9d5c0 100644 --- a/tests/check_chunking.c +++ b/tests/check_chunking.c @@ -49,13 +49,13 @@ START_TEST(encodeArrayIntoFiveChunksShallWork) { UA_Byte *pos = workingBuffer.data; const UA_Byte *end = &workingBuffer.data[workingBuffer.length]; UA_StatusCode retval = UA_encodeBinaryInternal(&v,&UA_TYPES[UA_TYPES_VARIANT], - &pos, &end, sendChunkMockUp, NULL); + &pos, &end, NULL, sendChunkMockUp, NULL); ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD); ck_assert_uint_eq(counter,4); //5 chunks allocated - callback called 4 times dataCount += (uintptr_t)(pos - buffers[bufIndex].data); - ck_assert_uint_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT]), dataCount); + ck_assert_uint_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT], NULL), dataCount); UA_Variant_clear(&v); UA_Array_delete(buffers, chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]); @@ -93,13 +93,13 @@ START_TEST(encodeStringIntoFiveChunksShallWork) { UA_Byte *pos = workingBuffer.data; const UA_Byte *end = &workingBuffer.data[workingBuffer.length]; UA_StatusCode retval = UA_encodeBinaryInternal(&v, &UA_TYPES[UA_TYPES_VARIANT], - &pos, &end, sendChunkMockUp, NULL); + &pos, &end, NULL, sendChunkMockUp, NULL); ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD); ck_assert_uint_eq(counter,4); //5 chunks allocated - callback called 4 times dataCount += (uintptr_t)(pos - buffers[bufIndex].data); - ck_assert_uint_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT]), dataCount); + ck_assert_uint_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT], NULL), dataCount); UA_Variant_clear(&v); UA_Array_delete(buffers, chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]); @@ -134,18 +134,18 @@ START_TEST(encodeTwoStringsIntoTenChunksShallWork) { UA_Byte *pos = workingBuffer.data; const UA_Byte *end = &workingBuffer.data[workingBuffer.length]; UA_StatusCode retval = UA_encodeBinaryInternal(&string, &UA_TYPES[UA_TYPES_STRING], - &pos, &end, sendChunkMockUp, NULL); + &pos, &end, NULL, sendChunkMockUp, NULL); ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD); ck_assert_uint_eq(counter,4); //5 chunks allocated - callback called 4 times size_t offset = (uintptr_t)(pos - buffers[bufIndex].data); - ck_assert_uint_eq(UA_calcSizeBinary(&string,&UA_TYPES[UA_TYPES_STRING]), dataCount + offset); + ck_assert_uint_eq(UA_calcSizeBinary(&string,&UA_TYPES[UA_TYPES_STRING], NULL), dataCount + offset); retval = UA_encodeBinaryInternal(&string,&UA_TYPES[UA_TYPES_STRING], - &pos, &end, sendChunkMockUp, NULL); + &pos, &end, NULL, sendChunkMockUp, NULL); dataCount += (uintptr_t)(pos - buffers[bufIndex].data); ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD); ck_assert_uint_eq(counter,9); //10 chunks allocated - callback called 4 times - ck_assert_uint_eq(2 * UA_calcSizeBinary(&string,&UA_TYPES[UA_TYPES_STRING]), dataCount); + ck_assert_uint_eq(2 * UA_calcSizeBinary(&string,&UA_TYPES[UA_TYPES_STRING], NULL), dataCount); UA_Array_delete(buffers, chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]); UA_String_clear(&string); diff --git a/tests/check_types_builtin.c b/tests/check_types_builtin.c index e833a9ebc..b6d451f11 100644 --- a/tests/check_types_builtin.c +++ b/tests/check_types_builtin.c @@ -23,7 +23,7 @@ enum UA_VARIANT_ENCODINGMASKTYPE_enum { static UA_Variant* EncodeDecodeVariant(UA_Variant* sourceVariant) { UA_ByteString encodedVariant; UA_ByteString_init(&encodedVariant); - UA_StatusCode encodeResult = UA_encodeBinary(sourceVariant, &UA_TYPES[UA_TYPES_VARIANT], &encodedVariant); + UA_StatusCode encodeResult = UA_encodeBinary(sourceVariant, &UA_TYPES[UA_TYPES_VARIANT], &encodedVariant, NULL); UA_Variant* decodedVariant = UA_Variant_new(); UA_StatusCode decodeResult = UA_decodeBinary(&encodedVariant, decodedVariant, &UA_TYPES[UA_TYPES_VARIANT], NULL ); @@ -97,7 +97,7 @@ START_TEST(UA_Byte_decodeShallCopyAndAdvancePosition) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 1); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_BYTE])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_BYTE], NULL)); ck_assert_uint_eq(dst, 0x08); } END_TEST @@ -367,7 +367,7 @@ START_TEST(UA_String_decodeShallAllocateMemoryAndCopyString) { ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(dst.length, 8); ck_assert_int_eq(dst.data[3], 'L'); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_STRING])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_STRING], NULL)); // finally UA_String_clear(&dst); } @@ -416,7 +416,7 @@ START_TEST(UA_NodeId_decodeTwoByteShallReadTwoBytesAndSetNamespaceToZero) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 2); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID], NULL)); ck_assert_int_eq(dst.identifierType, UA_NODEIDTYPE_NUMERIC); ck_assert_int_eq(dst.identifier.numeric, 16); ck_assert_int_eq(dst.namespaceIndex, 0); @@ -434,7 +434,7 @@ START_TEST(UA_NodeId_decodeFourByteShallReadFourBytesAndRespectNamespace) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 4); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID], NULL)); ck_assert_int_eq(dst.identifierType, UA_NODEIDTYPE_NUMERIC); ck_assert_int_eq(dst.identifier.numeric, 256); ck_assert_int_eq(dst.namespaceIndex, 1); @@ -452,7 +452,7 @@ START_TEST(UA_NodeId_decodeStringShallAllocateMemory) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 10); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_NODEID], NULL)); ck_assert_int_eq(dst.identifierType, UA_NODEIDTYPE_STRING); ck_assert_int_eq(dst.namespaceIndex, 1); ck_assert_uint_eq(dst.identifier.string.length, 3); @@ -473,7 +473,7 @@ START_TEST(UA_Variant_decodeWithOutArrayFlagSetShallSetVTAndAllocateMemoryForArr // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 5); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_VARIANT])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_VARIANT], NULL)); //ck_assert_ptr_eq((const void *)dst.type, (const void *)&UA_TYPES[UA_TYPES_INT32]); //does not compile in gcc 4.6 ck_assert_uint_eq((uintptr_t)dst.type, (uintptr_t)&UA_TYPES[UA_TYPES_INT32]); ck_assert_uint_eq(dst.arrayLength, 0); @@ -499,7 +499,7 @@ START_TEST(UA_Variant_decodeWithArrayFlagSetShallSetVTAndAllocateMemoryForArray) // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 1+4+2*4); - ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_VARIANT])); + ck_assert_uint_eq(pos, UA_calcSizeBinary(&dst, &UA_TYPES[UA_TYPES_VARIANT], NULL)); //ck_assert_ptr_eq((const (void*))dst.type, (const void*)&UA_TYPES[UA_TYPES_INT32]); //does not compile in gcc 4.6 ck_assert_uint_eq((uintptr_t)dst.type,(uintptr_t)&UA_TYPES[UA_TYPES_INT32]); ck_assert_uint_eq(dst.arrayLength, 2); @@ -929,7 +929,7 @@ START_TEST(UA_String_encodeShallWorkOnExample) { UA_StatusCode retval = UA_String_encodeBinary(&src, &pos, end); // then ck_assert_uint_eq((uintptr_t)(pos - dst.data), sizeof(UA_Int32)+11); - ck_assert_uint_eq(sizeof(UA_Int32)+11, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_STRING])); + ck_assert_uint_eq(sizeof(UA_Int32)+11, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_STRING], NULL)); ck_assert_int_eq(dst.data[0], 11); ck_assert_int_eq(dst.data[sizeof(UA_Int32)+0], 'A'); ck_assert_int_eq(dst.data[sizeof(UA_Int32)+1], 'C'); @@ -1006,7 +1006,7 @@ START_TEST(UA_ExpandedNodeId_encodeShallWorkOnExample) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_uint_eq((uintptr_t)(pos - dst.data), 13); - ck_assert_uint_eq(13, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_EXPANDEDNODEID])); + ck_assert_uint_eq(13, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL)); ck_assert_int_eq(dst.data[0], 0x80); // namespaceuri flag } END_TEST @@ -1029,7 +1029,7 @@ START_TEST(UA_DataValue_encodeShallWorkOnExampleWithoutVariant) { UA_StatusCode retval = UA_DataValue_encodeBinary(&src, &pos, end); // then ck_assert_uint_eq((uintptr_t)(pos - dst.data), 9); - ck_assert_uint_eq(9, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_DATAVALUE])); + ck_assert_uint_eq(9, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_DATAVALUE], NULL)); ck_assert_int_eq(dst.data[0], 0x08); // encodingMask ck_assert_int_eq(dst.data[1], 80); // 8 Byte serverTimestamp ck_assert_int_eq(dst.data[2], 0); @@ -1066,7 +1066,7 @@ START_TEST(UA_DataValue_encodeShallWorkOnExampleWithVariant) { UA_StatusCode retval = UA_DataValue_encodeBinary(&src, &pos, end); // then ck_assert_uint_eq((uintptr_t)(pos - dst.data), 1+(1+4)+8); // represents the length - ck_assert_uint_eq(1+(1+4)+8, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_DATAVALUE])); + ck_assert_uint_eq(1+(1+4)+8, UA_calcSizeBinary(&src, &UA_TYPES[UA_TYPES_DATAVALUE], NULL)); ck_assert_int_eq(dst.data[0], 0x08 | 0x01); // encodingMask ck_assert_int_eq(dst.data[1], 0x06); // Variant's Encoding Mask - INT32 ck_assert_int_eq(dst.data[2], 45); // the single value diff --git a/tests/check_types_custom.c b/tests/check_types_custom.c index 91d1484ef..d5fb371dd 100644 --- a/tests/check_types_custom.c +++ b/tests/check_types_custom.c @@ -298,12 +298,12 @@ START_TEST(parseCustomScalar) { UA_Variant_init(&var); UA_Variant_setScalar(&var, &p, &PointType); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; UA_StatusCode retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -334,12 +334,12 @@ START_TEST(parseCustomScalarExtensionObject) { eo.content.decoded.data = &p; eo.content.decoded.type = &PointType; - size_t buflen = UA_calcSizeBinary(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); + size_t buflen = UA_calcSizeBinary(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL); UA_ByteString buf; UA_StatusCode retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], &buf); + retval = UA_encodeBinary(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_ExtensionObject eo2; @@ -371,12 +371,12 @@ START_TEST(parseCustomArray) { UA_Variant_init(&var); UA_Variant_setArray(&var, (void*)ps, 10, &PointType); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; UA_StatusCode retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -416,12 +416,12 @@ START_TEST(parseCustomStructureWithOptionalFields) { UA_Variant_init(&var); UA_Variant_setScalarCopy(&var, &o, &OptType); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; UA_StatusCode retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -458,13 +458,13 @@ START_TEST(parseCustomStructureWithOptionalFieldsWithArrayNotContained) { UA_Variant_init(&var); retval = UA_Variant_setScalarCopy(&var, &oa, &ArrayOptType); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t binSize = UA_calcSizeBinary(&oa, &ArrayOptType); + size_t binSize = UA_calcSizeBinary(&oa, &ArrayOptType, NULL); ck_assert_uint_eq(binSize, 44); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -516,13 +516,13 @@ START_TEST(parseCustomStructureWithOptionalFieldsWithArrayContained) { UA_Variant_init(&var); retval = UA_Variant_setScalarCopy(&var, &oa, &ArrayOptType); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t binSize = UA_calcSizeBinary(&oa, &ArrayOptType); + size_t binSize = UA_calcSizeBinary(&oa, &ArrayOptType, NULL); ck_assert_uint_eq(binSize, 60); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; UA_DecodeBinaryOptions opt; @@ -564,16 +564,16 @@ START_TEST(parseCustomUnion) { retval = UA_Variant_setScalarCopy(&var, &u, &UniType); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t lengthOfUnion = UA_calcSizeBinary(&u, &UniType); + size_t lengthOfUnion = UA_calcSizeBinary(&u, &UniType, NULL); //check if 19 is the right size ck_assert_uint_eq(lengthOfUnion, 19); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -606,16 +606,16 @@ START_TEST(parseSelfContainingUnionNormalMember) { retval = UA_Variant_setScalarCopy(&var, &s, &selfContainingUnionType); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t lengthOfUnion = UA_calcSizeBinary(&s, &selfContainingUnionType); + size_t lengthOfUnion = UA_calcSizeBinary(&s, &selfContainingUnionType, NULL); //check if 12 is the right size ck_assert_uint_eq(lengthOfUnion, 12); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; @@ -651,18 +651,18 @@ START_TEST(parseSelfContainingUnionSelfMember) { retval = UA_Variant_setScalarCopy(&var, &s, &selfContainingUnionType); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - size_t lengthOfUnion = UA_calcSizeBinary(&s, &selfContainingUnionType); + size_t lengthOfUnion = UA_calcSizeBinary(&s, &selfContainingUnionType, NULL); //check if 32 is the right size ck_assert_uint_eq(lengthOfUnion, 32); UA_free(s.fields.array.array); - size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT]); + size_t buflen = UA_calcSizeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], NULL); UA_ByteString buf; retval = UA_ByteString_allocBuffer(&buf, buflen); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf); + retval = UA_encodeBinary(&var, &UA_TYPES[UA_TYPES_VARIANT], &buf, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant var2; diff --git a/tests/check_types_memory.c b/tests/check_types_memory.c index 46f082cf6..840722b13 100644 --- a/tests/check_types_memory.c +++ b/tests/check_types_memory.c @@ -61,7 +61,7 @@ START_TEST(encodeShallYieldDecode) { ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Byte *pos = msg1.data; const UA_Byte *end = &msg1.data[msg1.length]; - retval = UA_encodeBinaryInternal(obj1, &UA_TYPES[_i], &pos, &end, NULL, NULL); + retval = UA_encodeBinaryInternal(obj1, &UA_TYPES[_i], &pos, &end, NULL, NULL, NULL); if(retval != UA_STATUSCODE_GOOD) { UA_delete(obj1, &UA_TYPES[_i]); UA_ByteString_clear(&msg1); @@ -79,7 +79,7 @@ START_TEST(encodeShallYieldDecode) { ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); pos = msg2.data; end = &msg2.data[msg2.length]; - retval = UA_encodeBinaryInternal(obj2, &UA_TYPES[_i], &pos, &end, NULL, NULL); + retval = UA_encodeBinaryInternal(obj2, &UA_TYPES[_i], &pos, &end, NULL, NULL, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); // then @@ -115,7 +115,7 @@ START_TEST(decodeShallFailWithTruncatedBufferButSurvive) { UA_StatusCode retval = UA_ByteString_allocBuffer(&msg1, 65000); // fixed buf size UA_Byte *pos = msg1.data; const UA_Byte *end = &msg1.data[msg1.length]; - retval |= UA_encodeBinaryInternal(obj1, &UA_TYPES[_i], &pos, &end, NULL, NULL); + retval |= UA_encodeBinaryInternal(obj1, &UA_TYPES[_i], &pos, &end, NULL, NULL, NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_delete(obj1, &UA_TYPES[_i]); @@ -208,14 +208,14 @@ END_TEST START_TEST(calcSizeBinaryShallBeCorrect) { void *obj = UA_new(&UA_TYPES[_i]); - size_t predicted_size = UA_calcSizeBinary(obj, &UA_TYPES[_i]); + size_t predicted_size = UA_calcSizeBinary(obj, &UA_TYPES[_i], NULL); ck_assert_uint_ne(predicted_size, 0); UA_ByteString msg; UA_StatusCode retval = UA_ByteString_allocBuffer(&msg, predicted_size); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); UA_Byte *pos = msg.data; const UA_Byte *end = &msg.data[msg.length]; - retval = UA_encodeBinaryInternal(obj, &UA_TYPES[_i], &pos, &end, NULL, NULL); + retval = UA_encodeBinaryInternal(obj, &UA_TYPES[_i], &pos, &end, NULL, NULL, NULL); if(retval) printf("%i\n",_i); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); diff --git a/tests/encryption/check_gds_informationmodel.c b/tests/encryption/check_gds_informationmodel.c index f159b7c06..ebf737d41 100644 --- a/tests/encryption/check_gds_informationmodel.c +++ b/tests/encryption/check_gds_informationmodel.c @@ -351,7 +351,7 @@ START_TEST(rw_trustlist) { fd = *(UA_Int32*)fileHandler.data; UA_ByteString encTrustList = UA_BYTESTRING_NULL; - retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList); + retval = UA_encodeBinary(&trustList, &UA_TYPES[UA_TYPES_TRUSTLISTDATATYPE], &encTrustList, NULL); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); UA_ByteString chunk1 = {.length = 1000, .data = encTrustList.data}; diff --git a/tests/fuzz/fuzz_binary_decode.cc b/tests/fuzz/fuzz_binary_decode.cc index 9de956bea..d7c22102b 100644 --- a/tests/fuzz/fuzz_binary_decode.cc +++ b/tests/fuzz/fuzz_binary_decode.cc @@ -67,7 +67,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { UA_delete(dstCopy, &UA_TYPES[typeIndex]); // now also test encoding - size_t encSize = UA_calcSizeBinary(dst, &UA_TYPES[typeIndex]); + size_t encSize = UA_calcSizeBinary(dst, &UA_TYPES[typeIndex], NULL); UA_ByteString encoded; ret = UA_ByteString_allocBuffer(&encoded, encSize); if(ret != UA_STATUSCODE_GOOD) { @@ -75,7 +75,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; } - ret = UA_encodeBinary(dst, &UA_TYPES[typeIndex], &encoded); + ret = UA_encodeBinary(dst, &UA_TYPES[typeIndex], &encoded, NULL); UA_assert(ret == UA_STATUSCODE_GOOD); UA_ByteString_clear(&encoded); diff --git a/tests/pubsub/attic/check_pubsub_connection_xdp.c b/tests/pubsub/attic/check_pubsub_connection_xdp.c index 1c8e822ff..e6599bc23 100644 --- a/tests/pubsub/attic/check_pubsub_connection_xdp.c +++ b/tests/pubsub/attic/check_pubsub_connection_xdp.c @@ -274,8 +274,8 @@ START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); - ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) == - UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE])); + ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL) == + UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL)); for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties.map[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties.map[i].value)); diff --git a/tests/pubsub/check_pubsub_connection_ethernet.c b/tests/pubsub/check_pubsub_connection_ethernet.c index d2490fb64..dae343f53 100644 --- a/tests/pubsub/check_pubsub_connection_ethernet.c +++ b/tests/pubsub/check_pubsub_connection_ethernet.c @@ -210,8 +210,8 @@ START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); - ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) == - UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE])); + ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL) == + UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL)); for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties.map[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties.map[i].value)); diff --git a/tests/pubsub/check_pubsub_connection_mqtt.c b/tests/pubsub/check_pubsub_connection_mqtt.c index 5b460d95c..d90dd2238 100644 --- a/tests/pubsub/check_pubsub_connection_mqtt.c +++ b/tests/pubsub/check_pubsub_connection_mqtt.c @@ -204,9 +204,9 @@ START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) == + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL) == UA_calcSizeBinary(&networkAddressUrlData, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE])); + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL)); for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); diff --git a/tests/pubsub/check_pubsub_connection_udp.c b/tests/pubsub/check_pubsub_connection_udp.c index fcbc741b4..718d80b8a 100644 --- a/tests/pubsub/check_pubsub_connection_udp.c +++ b/tests/pubsub/check_pubsub_connection_udp.c @@ -244,8 +244,8 @@ START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); - ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) == - UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE])); + ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL) == + UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL)); for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties.map[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties.map[i].value)); diff --git a/tests/server/check_server_readspeed.c b/tests/server/check_server_readspeed.c index a45709553..9e00feedc 100644 --- a/tests/server/check_server_readspeed.c +++ b/tests/server/check_server_readspeed.c @@ -156,7 +156,7 @@ START_TEST(readSpeedWithEncoding) { UA_ByteString request_msg = request_buffer; /* Encode the request */ - retval = UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_READREQUEST], &request_msg); + retval = UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_READREQUEST], &request_msg, NULL); ck_assert(retval == UA_STATUSCODE_GOOD); /* Decode the request */ @@ -167,7 +167,7 @@ START_TEST(readSpeedWithEncoding) { Service_Read(server, &server->adminSession, &req, &res); UA_UNLOCK(&server->serviceMutex); - retval = UA_encodeBinary(&res, &UA_TYPES[UA_TYPES_READRESPONSE], &response_msg); + retval = UA_encodeBinary(&res, &UA_TYPES[UA_TYPES_READRESPONSE], &response_msg, NULL); ck_assert(retval == UA_STATUSCODE_GOOD); UA_ReadRequest_clear(&req); diff --git a/tools/ua2json/ua2json.c b/tools/ua2json/ua2json.c index 71183ea49..6b4b606ab 100644 --- a/tools/ua2json/ua2json.c +++ b/tools/ua2json/ua2json.c @@ -55,7 +55,7 @@ decode(const UA_ByteString *buf, UA_ByteString *out, const UA_DataType *type) { } /* Encode Binary. Internally allocates the buffer upon success */ - retval = UA_encodeBinary(data, type, out); + retval = UA_encodeBinary(data, type, out, NULL); /* Clean up */ UA_delete(data, type); From 2f0418dd36c53fe98075ca8ead49faabd1728c9f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 12 Aug 2024 22:04:20 +0200 Subject: [PATCH 003/158] feat(core): Use the namespace mapping automatically during binary de/encoding --- src/ua_types_encoding_binary.c | 41 ++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/ua_types_encoding_binary.c b/src/ua_types_encoding_binary.c index 0a0e60ede..3b31c9fe5 100644 --- a/src/ua_types_encoding_binary.c +++ b/src/ua_types_encoding_binary.c @@ -598,18 +598,25 @@ FUNC_DECODE_BINARY(Guid) { static status NodeId_encodeBinaryWithEncodingMask(Ctx *ctx, UA_NodeId const *src, u8 encoding) { status ret = UA_STATUSCODE_GOOD; + + /* Mapping of the namespace index */ + UA_UInt16 nsIndex = src->namespaceIndex; + if(ctx->opts.namespaceMapping) + nsIndex = UA_NamespaceMapping_local2Remote(ctx->opts.namespaceMapping, + nsIndex); + switch(src->identifierType) { case UA_NODEIDTYPE_NUMERIC: - if(src->identifier.numeric > UA_UINT16_MAX || src->namespaceIndex > UA_BYTE_MAX) { + if(src->identifier.numeric > UA_UINT16_MAX || nsIndex > UA_BYTE_MAX) { encoding |= UA_NODEIDTYPE_NUMERIC_COMPLETE; ret |= ENCODE_DIRECT(&encoding, Byte); - ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT(&nsIndex, UInt16); ret |= ENCODE_DIRECT(&src->identifier.numeric, UInt32); - } else if(src->identifier.numeric > UA_BYTE_MAX || src->namespaceIndex > 0) { + } else if(src->identifier.numeric > UA_BYTE_MAX || nsIndex > 0) { encoding |= UA_NODEIDTYPE_NUMERIC_FOURBYTE; ret |= ENCODE_DIRECT(&encoding, Byte); - u8 nsindex = (u8)src->namespaceIndex; - ret |= ENCODE_DIRECT(&nsindex, Byte); + u8 nsindex8 = (u8)nsIndex; + ret |= ENCODE_DIRECT(&nsindex8, Byte); u16 identifier16 = (u16)src->identifier.numeric; ret |= ENCODE_DIRECT(&identifier16, UInt16); } else { @@ -622,7 +629,7 @@ NodeId_encodeBinaryWithEncodingMask(Ctx *ctx, UA_NodeId const *src, u8 encoding) case UA_NODEIDTYPE_STRING: encoding |= (u8)UA_NODEIDTYPE_STRING; ret |= ENCODE_DIRECT(&encoding, Byte); - ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT(&nsIndex, UInt16); UA_CHECK_STATUS(ret, return ret); /* Can exchange the buffer */ ret = ENCODE_DIRECT(&src->identifier.string, String); @@ -631,13 +638,13 @@ NodeId_encodeBinaryWithEncodingMask(Ctx *ctx, UA_NodeId const *src, u8 encoding) case UA_NODEIDTYPE_GUID: encoding |= (u8)UA_NODEIDTYPE_GUID; ret |= ENCODE_DIRECT(&encoding, Byte); - ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT(&nsIndex, UInt16); ret |= ENCODE_DIRECT(&src->identifier.guid, Guid); break; case UA_NODEIDTYPE_BYTESTRING: encoding |= (u8)UA_NODEIDTYPE_BYTESTRING; ret |= ENCODE_DIRECT(&encoding, Byte); - ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT(&nsIndex, UInt16); UA_CHECK_STATUS(ret, return ret); /* Can exchange the buffer */ ret = ENCODE_DIRECT(&src->identifier.byteString, String); /* ByteString */ @@ -704,6 +711,13 @@ FUNC_DECODE_BINARY(NodeId) { ret |= UA_STATUSCODE_BADINTERNALERROR; break; } + + /* Mapping of the namespace index */ + if(ctx->opts.namespaceMapping) + dst->namespaceIndex = + UA_NamespaceMapping_remote2Local(ctx->opts.namespaceMapping, + dst->namespaceIndex); + return ret; } @@ -748,6 +762,15 @@ FUNC_DECODE_BINARY(ExpandedNodeId) { if(encoding & UA_EXPANDEDNODEID_NAMESPACEURI_FLAG) { dst->nodeId.namespaceIndex = 0; ret |= DECODE_DIRECT(&dst->namespaceUri, String); + /* Try to resolve the namespace uri to a namespace index */ + if(ctx->opts.namespaceMapping) { + status foundNsUri = + UA_NamespaceMapping_uri2Index(ctx->opts.namespaceMapping, + dst->namespaceUri, + &dst->nodeId.namespaceIndex); + if(foundNsUri == UA_STATUSCODE_GOOD) + UA_String_clear(&dst->namespaceUri); + } } /* Decode the ServerIndex */ @@ -1654,12 +1677,12 @@ UA_encodeBinaryInternal(const void *src, const UA_DataType *type, /* Set up the context */ Ctx ctx; + memset(&ctx, 0, sizeof(Ctx)); ctx.pos = *bufPos; ctx.end = *bufEnd; ctx.depth = 0; ctx.exchangeBufferCallback = exchangeCallback; ctx.exchangeBufferCallbackHandle = exchangeHandle; - memset(&ctx.opts, 0, sizeof(UA_DecodeBinaryOptions)); if(options) ctx.opts.namespaceMapping = options->namespaceMapping; From aa332a40735f27e037d92eaf261480ba67e2ffa0 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 12 Aug 2024 22:50:55 +0200 Subject: [PATCH 004/158] feat(client): Add the API to configure static namespace indices --- include/open62541/client.h | 40 ++++++++++++++++++ src/client/ua_client.c | 72 +++++++++++++++++++++++++++++++++ src/client/ua_client_internal.h | 5 +++ 3 files changed, 117 insertions(+) diff --git a/include/open62541/client.h b/include/open62541/client.h index 307fc4f5e..f2ec7ca29 100644 --- a/include/open62541/client.h +++ b/include/open62541/client.h @@ -145,6 +145,32 @@ struct UA_ClientConfig { * data types are provided in ``/examples/custom_datatype/``. */ const UA_DataTypeArray *customDataTypes; + /** + * Namespace Mapping + * ~~~~~~~~~~~~~~~~~ + * The namespaces index is "just" a mapping to the Uris in the namespace + * array of the server. In order to have stable NodeIds across servers, the + * client keeps a list of predefined namespaces. Use + * ``UA_Client_addNamespaceUri``, ``UA_Client_getNamespaceUri`` and + * ``UA_Client_getNamespaceIndex`` to interact with the local namespace + * mapping. + * + * The namespace indices are assigned internally in the client as follows: + * + * - Ns0 and Ns1 are pre-defined by the standard. Ns0 is always + * ```http://opcfoundation.org/UA/``` and used for standard-defined + * NodeIds. Ns1 corresponds to the application uri of the individual + * server. + * - The next namespaces are added in-order from the list below at startup + * (starting at index 2). + * - The local API ``UA_Client_addNamespaceUri`` can be used to add more + * namespaces. + * - When the client connects, the namespace array of the server is read. + * All previously unknown namespaces are added from this to the internal + * array of the client. */ + UA_String *namespaces; + size_t namespacesSize; + /** * Advanced Client Configuration * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -953,6 +979,20 @@ UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId); UA_EXPORT const UA_DataType * UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId); +/* The string is allocated and needs to be cleared */ +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index, + UA_String *nsUri); + +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri, + UA_UInt16 *outIndex); + +/* Returns the old index of the namespace already exists */ +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Client_addNamespace(UA_Client *client, const UA_String nsUri, + UA_UInt16 *outIndex); + /** * .. toctree:: * diff --git a/src/client/ua_client.c b/src/client/ua_client.c index fed71340a..67d266d40 100644 --- a/src/client/ua_client.c +++ b/src/client/ua_client.c @@ -121,7 +121,27 @@ UA_Client_newWithConfig(const UA_ClientConfig *config) { UA_LOCK_INIT(&client->clientMutex); #endif + /* Initialize the namespace mapping */ + size_t initialNs = 2 + config->namespacesSize; + client->namespaces = (UA_String*)UA_calloc(initialNs, sizeof(UA_String)); + if(!client->namespaces) + goto error; + client->namespacesSize = initialNs; + client->namespaces[0] = UA_STRING_ALLOC("http://opcfoundation.org/UA/"); + client->namespaces[1] = UA_STRING_NULL; /* Gets set when we connect to the server */ + UA_StatusCode res = UA_STATUSCODE_GOOD; + for(size_t i = 0; i < config->namespacesSize; i++) { + res |= UA_String_copy(&client->namespaces[i+2], &config->namespaces[i]); + } + if(res != UA_STATUSCODE_GOOD) + goto error; + return client; + +error: + memset(&client->config, 0, sizeof(UA_ClientConfig)); + UA_Client_delete(client); + return NULL; } void @@ -226,8 +246,15 @@ UA_Client_clear(UA_Client *client) { UA_Client_removeCallback(client, client->houseKeepingCallbackId); client->houseKeepingCallbackId = 0; + /* Clean up the SecureChannel */ UA_SecureChannel_clear(&client->channel); + /* Free the namespace mapping */ + UA_Array_delete(client->namespaces, client->namespacesSize, + &UA_TYPES[UA_TYPES_STRING]); + client->namespaces = NULL; + client->namespacesSize = 0; + #if UA_MULTITHREADING >= 100 UA_LOCK_DESTROY(&client->clientMutex); #endif @@ -1168,3 +1195,48 @@ UA_Client_getConnectionAttribute_scalar(UA_Client *client, UA_UNLOCK(&client->clientMutex); return UA_STATUSCODE_GOOD; } + +/* Namespace Mapping */ + +UA_StatusCode +UA_Client_getNamespaceUri(UA_Client *client, UA_UInt16 index, + UA_String *nsUri) { + UA_LOCK(&client->clientMutex); + UA_StatusCode res = UA_STATUSCODE_GOOD; + if(index > client->namespacesSize) + res = UA_String_copy(&client->namespaces[index], nsUri); + else + res = UA_STATUSCODE_BADNOTFOUND; + UA_UNLOCK(&client->clientMutex); + return res; +} + +UA_StatusCode +UA_Client_getNamespaceIndex(UA_Client *client, const UA_String nsUri, + UA_UInt16 *outIndex) { + UA_LOCK(&client->clientMutex); + for(size_t i = 0; i < client->namespacesSize; i++) { + if(UA_String_equal(&nsUri, &client->namespaces[i])) { + *outIndex = (UA_UInt16)i; + UA_UNLOCK(&client->clientMutex); + return UA_STATUSCODE_GOOD; + } + } + UA_UNLOCK(&client->clientMutex); + return UA_STATUSCODE_BADNOTFOUND; +} + +UA_StatusCode +UA_Client_addNamespace(UA_Client *client, const UA_String nsUri, + UA_UInt16 *outIndex) { + UA_StatusCode res = UA_Client_getNamespaceIndex(client, nsUri, outIndex); + if(res == UA_STATUSCODE_GOOD) + return res; + UA_LOCK(&client->clientMutex); + res = UA_Array_appendCopy((void**)&client->namespaces, &client->namespacesSize, + &nsUri, &UA_TYPES[UA_TYPES_STRING]); + if(res == UA_STATUSCODE_GOOD) + *outIndex = (UA_UInt16)(client->namespacesSize - 1); + UA_UNLOCK(&client->clientMutex); + return res; +} diff --git a/src/client/ua_client_internal.h b/src/client/ua_client_internal.h index 45272b677..7e59b9e7d 100644 --- a/src/client/ua_client_internal.h +++ b/src/client/ua_client_internal.h @@ -167,6 +167,11 @@ struct UA_Client { UA_UInt32 monitoredItemHandles; UA_UInt16 currentlyOutStandingPublishRequests; + /* Internal namespaces. The table maps the namespace Uri to its index. This + * is used for the automatic namespace mapping in de/encoding. */ + UA_String *namespaces; + size_t namespacesSize; + /* Internal locking for thread-safety. Methods starting with UA_Client_ that * are marked with UA_THREADSAFE take the lock. The lock is released before * dropping into the EventLoop and before calling user-defined callbacks. From 9266a1cbd3288e191e5133bedba57cd1d5674b74 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 12 Aug 2024 23:12:40 +0200 Subject: [PATCH 005/158] feat(client): Read the namespaces array after opening the session --- src/client/ua_client_connect.c | 57 +++++++++++++++++++++++++++++++++ src/client/ua_client_internal.h | 2 ++ 2 files changed, 59 insertions(+) diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index de40c6993..6561d27c6 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -25,6 +25,7 @@ * UA_Client and reconnect. * - Call GetEndpoints and select an Endpoint * - Open a SecureChannel and Session for that Endpoint + * - Read the namespaces array of the server (and create the namespaces mapping) */ #define UA_MINMESSAGESIZE 8192 @@ -652,6 +653,58 @@ UA_Client_renewSecureChannel(UA_Client *client) { return res; } +static void +responseReadNamespacesArray(UA_Client *client, void *userdata, UA_UInt32 requestId, + void *response) { + client->namespacesHandshake = false; + client->haveNamespaces = true; + + /* TODO: Add received namespaces to the local array. Set up the mapping. */ +} + +/* We read the namespaces right after the session has opened. The user might + * already requests other services in parallel. That leaves a short time where + * requests can be made before the namespace mapping is configured. */ +static void +readNamespacesArrayAsync(UA_Client *client) { + UA_LOCK_ASSERT(&client->clientMutex); + + /* Check the connection status */ + if(client->sessionState != UA_SESSIONSTATE_CREATED && + client->sessionState != UA_SESSIONSTATE_ACTIVATED) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Cannot read the namespaces array, session neither created nor " + "activated. Actual state: '%u'", client->sessionState); + return; + } + + /* Set up the read request */ + UA_ReadRequest rr; + UA_ReadRequest_init(&rr); + + UA_ReadValueId nodesToRead; + UA_ReadValueId_init(&nodesToRead); + nodesToRead.nodeId = UA_NS0ID(SERVER_NAMESPACEARRAY); + nodesToRead.attributeId = UA_ATTRIBUTEID_VALUE; + + rr.nodesToRead = &nodesToRead; + rr.nodesToReadSize = 1; + + /* Send the async read request */ + UA_StatusCode res = + __Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST], + (UA_ClientAsyncServiceCallback)responseReadNamespacesArray, + &UA_TYPES[UA_TYPES_READRESPONSE], + NULL, NULL); + + if(res == UA_STATUSCODE_GOOD) + client->namespacesHandshake = true; + else + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Could not read the namespace array with error code %s", + UA_StatusCode_name(res)); +} + static void responseActivateSession(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) { @@ -703,6 +756,10 @@ responseActivateSession(UA_Client *client, void *userdata, client->sessionState = UA_SESSIONSTATE_ACTIVATED; notifyClientState(client); + /* Read the namespaces array if we don't already have it */ + if(!client->haveNamespaces) + readNamespacesArrayAsync(client); + /* Immediately check if publish requests are outstanding - for example when * an existing Session has been reattached / activated. */ #ifdef UA_ENABLE_SUBSCRIPTIONS diff --git a/src/client/ua_client_internal.h b/src/client/ua_client_internal.h index 7e59b9e7d..b18ae6abc 100644 --- a/src/client/ua_client_internal.h +++ b/src/client/ua_client_internal.h @@ -125,6 +125,8 @@ struct UA_Client { UA_Boolean findServersHandshake; /* Ongoing FindServers */ UA_Boolean endpointsHandshake; /* Ongoing GetEndpoints */ + UA_Boolean namespacesHandshake; /* Ongoing Namespaces read */ + UA_Boolean haveNamespaces; /* Do we have the namespaces? */ /* The discoveryUrl can be different from the EndpointUrl in the client * configuration. The EndpointUrl is used to connect initially, then the From 847c0a0daff427842881a3986ba1af9c7baf839d Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Fri, 22 Nov 2024 08:50:36 +0100 Subject: [PATCH 006/158] feat(core): Clean up the namespace mapping table when clearing the secure channel --- src/ua_securechannel.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index 378174eb1..9f841b73e 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -201,6 +201,10 @@ UA_SecureChannel_clear(UA_SecureChannel *channel) { /* Delete remaining chunks */ UA_SecureChannel_deleteBuffered(channel); + /* Clean up namespace mapping */ + UA_NamespaceMapping_delete(channel->namespaceMapping); + channel->namespaceMapping = NULL; + /* Reset the SecureChannel for reuse (in the client) */ channel->securityMode = UA_MESSAGESECURITYMODE_INVALID; channel->shutdownReason = UA_SHUTDOWNREASON_CLOSE; From bfd19fc4f86badb88ff92cfb534ec5b57841bcdb Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Fri, 22 Nov 2024 08:54:02 +0100 Subject: [PATCH 007/158] feat(client): Create the namespace mapping table after opening the session --- src/client/ua_client_connect.c | 65 +++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index 6561d27c6..09273bffb 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -659,7 +659,70 @@ responseReadNamespacesArray(UA_Client *client, void *userdata, UA_UInt32 request client->namespacesHandshake = false; client->haveNamespaces = true; - /* TODO: Add received namespaces to the local array. Set up the mapping. */ + UA_ReadResponse *resp = (UA_ReadResponse *)response; + + /* Add received namespaces to the local array. */ + if(!resp->results || !resp->results[0].value.data) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "No result in the read namespace array response"); + return; + } + UA_String *ns = (UA_String *)resp->results[0].value.data; + size_t nsSize = resp->results[0].value.arrayLength; + UA_String_copy(&ns[1], &client->namespaces[1]); + for(size_t i = 2; i < nsSize; ++i) { + UA_UInt16 nsIndex = 0; + UA_Client_addNamespace(client, ns[i], &nsIndex); + } + + /* Set up the mapping. */ + UA_NamespaceMapping *nsMapping = (UA_NamespaceMapping*)UA_calloc(1, sizeof(UA_NamespaceMapping)); + if(!nsMapping) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Namespace mapping creation failed. Out of Memory."); + return; + } + UA_StatusCode retval = UA_Array_copy(client->namespaces, client->namespacesSize, (void**)&nsMapping->namespaceUris, &UA_TYPES[UA_TYPES_STRING]); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Failed to copy the namespaces with StatusCode %s.", + UA_StatusCode_name(retval)); + return; + } + nsMapping->namespaceUrisSize = client->namespacesSize; + + nsMapping->remote2local = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16)); + if(!nsMapping->remote2local) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Namespace mapping creation failed. Out of Memory."); + return; + } + nsMapping->remote2localSize = nsSize; + nsMapping->remote2local[0] = 0; + nsMapping->remote2local[1] = 1; + + for(size_t i = 2; i < nsSize; ++i) { + UA_UInt16 nsIndex = 0; + UA_Client_getNamespaceIndex(client, ns[i], &nsIndex); + nsMapping->remote2local[i] = nsIndex; + } + + nsMapping->local2remote = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16)); + if(!nsMapping->local2remote) { + UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, + "Namespace mapping creation failed. Out of Memory."); + return; + } + nsMapping->local2remoteSize = nsSize; + nsMapping->local2remote[0] = 0; + nsMapping->local2remote[1] = 1; + + for(size_t i = 2; i < nsMapping->remote2localSize; ++i) { + UA_UInt16 localIndex = nsMapping->remote2local[i]; + nsMapping->local2remote[localIndex] = (UA_UInt16)i; + } + + client->channel.namespaceMapping = nsMapping; } /* We read the namespaces right after the session has opened. The user might From c2cbb28536943ea46e69ea7e9e54bcf4038dce51 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Fri, 22 Nov 2024 08:59:35 +0100 Subject: [PATCH 008/158] feat(tests): Add test case to check the namespace mapping table --- tests/nodeset-compiler/CMakeLists.txt | 7 ++ .../nodeset-compiler/check_client_nsMapping.c | 106 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 tests/nodeset-compiler/check_client_nsMapping.c diff --git a/tests/nodeset-compiler/CMakeLists.txt b/tests/nodeset-compiler/CMakeLists.txt index 3458c154d..03f7d4417 100644 --- a/tests/nodeset-compiler/CMakeLists.txt +++ b/tests/nodeset-compiler/CMakeLists.txt @@ -54,6 +54,13 @@ if(UA_NAMESPACE_ZERO STREQUAL "FULL") ${UA_TYPES_TESTS_PLC_SOURCES}) add_dependencies(check_nodeset_compiler_plc open62541-generator-ns-tests-plc) + ua_add_test(check_client_nsMapping.c + ${UA_NODESET_TESTS_DI_SOURCES} + ${UA_NODESET_TESTS_PLC_SOURCES} + ${UA_TYPES_TESTS_DI_SOURCES} + ${UA_TYPES_TESTS_PLC_SOURCES}) + add_dependencies(check_client_nsMapping open62541-generator-ns-tests-plc) + # generate AutoID namespace which is using DI (test e.g. for structures with optional fields) ua_generate_nodeset_and_datatypes( NAME "tests-autoid" diff --git a/tests/nodeset-compiler/check_client_nsMapping.c b/tests/nodeset-compiler/check_client_nsMapping.c new file mode 100644 index 000000000..82ccb4205 --- /dev/null +++ b/tests/nodeset-compiler/check_client_nsMapping.c @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf) + * + */ + +#include <../../plugins/include/open62541/client_config_default.h> + +#include +#include + +#include "tests/namespace_tests_di_generated.h" +#include "tests/namespace_tests_plc_generated.h" + +#include "test_helpers.h" +#include "thread_wrapper.h" +#include "client/ua_client_internal.h" + +UA_Server *server; +UA_Boolean running; +THREAD_HANDLE server_thread; + +THREAD_CALLBACK(serverloop) { + while(running) + UA_Server_run_iterate(server, true); + return 0; +} + +static void setup(void) { + running = true; + server = UA_Server_newForUnitTest(); + ck_assert(server != NULL); + + size_t idx = LONG_MAX; + UA_StatusCode retval = UA_Server_getNamespaceByName(server, UA_STRING("http://opcfoundation.org/UA/DI/"), &idx); + if(retval != UA_STATUSCODE_GOOD) { + retval = namespace_tests_di_generated(server); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + } + retval = UA_Server_getNamespaceByName(server, UA_STRING("http://PLCopen.org/OpcUa/IEC61131-3/"), &idx); + if(retval != UA_STATUSCODE_GOOD) { + retval = namespace_tests_plc_generated(server); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + } + + UA_Server_run_startup(server); + THREAD_CREATE(server_thread, serverloop); +} + +static void teardown(void) { + running = false; + THREAD_JOIN(server_thread); + UA_Server_run_shutdown(server); + UA_Server_delete(server); +} + +START_TEST(Client_nsMapping){ + UA_Client *client = UA_Client_newForUnitTest(); + UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); + + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + + size_t max_stop_iteration_count = 100000; + size_t iteration = 0; + while(!client->haveNamespaces && iteration < max_stop_iteration_count) { + UA_Client_run_iterate(client, 0); + iteration++; + } + + UA_UInt16 idx = UA_NamespaceMapping_local2Remote(client->channel.namespaceMapping, 2); + ck_assert_uint_eq(idx, 2); + + idx = UA_NamespaceMapping_local2Remote(client->channel.namespaceMapping, 3); + ck_assert_uint_eq(idx, 3); + + idx = UA_NamespaceMapping_remote2Local(client->channel.namespaceMapping, 2); + ck_assert_uint_eq(idx, 2); + + idx = UA_NamespaceMapping_remote2Local(client->channel.namespaceMapping, 3); + ck_assert_uint_eq(idx, 3); + + UA_Client_disconnect(client); + UA_Client_delete(client); +} +END_TEST + +static Suite* testSuite_Client(void) { + Suite *s = suite_create("Client"); + TCase *tc_client = tcase_create("Client Namespace Mapping"); + tcase_add_checked_fixture(tc_client, setup, teardown); + tcase_add_test(tc_client, Client_nsMapping); + suite_add_tcase(s,tc_client); + return s; +} + +int main(void) { + Suite *s = testSuite_Client(); + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr,CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} From d5ce01cee109543e800e1f55fa807adea0beb491 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Fri, 29 Nov 2024 12:34:21 +0100 Subject: [PATCH 009/158] fix(tests): Fixed timing issues in tests caused by the generation of the namespace mapping table --- tests/client/check_client_async.c | 11 +++++++++++ tests/client/check_client_securechannel.c | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/client/check_client_async.c b/tests/client/check_client_async.c index ce0467db1..ac582422f 100644 --- a/tests/client/check_client_async.c +++ b/tests/client/check_client_async.c @@ -76,6 +76,14 @@ START_TEST(Client_highlevel_async_readValue) { UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* To generate the namespace mapping table */ + size_t max_stop_iteration_count = 100000; + size_t iteration = 0; + while(!client->haveNamespaces && iteration < max_stop_iteration_count) { + UA_Client_run_iterate(client, 0); + iteration++; + } + UA_UInt16 asyncCounter = 0; UA_UInt32 reqId = 0; retval = UA_Client_readValueAttribute_async(client, @@ -179,6 +187,9 @@ START_TEST(Client_read_async_timed) { UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* To generate the namespace mapping table */ + UA_Client_run_iterate(client, 1); + UA_UInt16 asyncCounter = 0; UA_ReadRequest rr; diff --git a/tests/client/check_client_securechannel.c b/tests/client/check_client_securechannel.c index 43db7195a..b58e87d9e 100644 --- a/tests/client/check_client_securechannel.c +++ b/tests/client/check_client_securechannel.c @@ -55,6 +55,9 @@ START_TEST(SecureChannel_timeout_max) { UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* To generate the namespace mapping table */ + UA_Client_run_iterate(client, 1); + UA_ClientConfig *cconfig = UA_Client_getConfig(client); UA_fakeSleep(cconfig->secureChannelLifeTime); @@ -75,6 +78,14 @@ START_TEST(SecureChannel_renew) { UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + /* To generate the namespace mapping table */ + size_t max_stop_iteration_count = 100000; + size_t iteration = 0; + while(!client->haveNamespaces && iteration < max_stop_iteration_count) { + UA_Client_run_iterate(client, 0); + iteration++; + } + pauseServer(); /* Get the channel id */ From b7fa6bae2ba1c1e54cca29539bf0c089cada5896 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 7 Dec 2024 23:37:07 +0100 Subject: [PATCH 010/158] refactor(core): Use NamespaceMapping for the JSON encoding --- include/open62541/types.h | 6 ++-- src/pubsub/ua_pubsub_networkmessage.h | 4 +-- src/pubsub/ua_pubsub_networkmessage_json.c | 16 ++++----- src/pubsub/ua_pubsub_writergroup.c | 4 +-- src/ua_types_encoding_json.c | 42 ++++++++++++---------- src/ua_types_encoding_json.h | 3 +- tests/check_types_builtin_json.c | 33 ++++++++++++----- tests/pubsub/check_pubsub_encoding_json.c | 8 ++--- 8 files changed, 68 insertions(+), 48 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 818d27784..4ca32c7f1 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1381,8 +1381,10 @@ UA_decodeBinary(const UA_ByteString *inBuf, #ifdef UA_ENABLE_JSON_ENCODING typedef struct { - const UA_String *namespaces; - size_t namespacesSize; + /* Mapping of namespace indices in NodeIds and of NamespaceUris in + * ExpandedNodeIds. */ + UA_NamespaceMapping *namespaceMapping; + const UA_String *serverUris; size_t serverUrisSize; UA_Boolean useReversible; diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index e8685c335..4fd42d69c 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -138,13 +138,13 @@ UA_NetworkMessage_decodeFooters(Ctx *ctx, UA_NetworkMessage *dst); UA_StatusCode UA_NetworkMessage_encodeJsonInternal(const UA_NetworkMessage *src, UA_Byte **bufPos, const UA_Byte **bufEnd, - UA_String *namespaces, size_t namespaceSize, + UA_NamespaceMapping *namespaceMapping, UA_String *serverUris, size_t serverUriSize, UA_Boolean useReversible); size_t UA_NetworkMessage_calcSizeJsonInternal(const UA_NetworkMessage *src, - UA_String *namespaces, size_t namespaceSize, + UA_NamespaceMapping *namespaceMapping, UA_String *serverUris, size_t serverUriSize, UA_Boolean useReversible); diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index 31c68d065..04354de77 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -196,7 +196,7 @@ UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx UA_StatusCode UA_NetworkMessage_encodeJsonInternal(const UA_NetworkMessage *src, UA_Byte **bufPos, const UA_Byte **bufEnd, - UA_String *namespaces, size_t namespaceSize, + UA_NamespaceMapping *namespaceMapping, UA_String *serverUris, size_t serverUriSize, UA_Boolean useReversible) { /* Set up the context */ @@ -205,8 +205,7 @@ UA_NetworkMessage_encodeJsonInternal(const UA_NetworkMessage *src, ctx.pos = *bufPos; ctx.end = *bufEnd; ctx.depth = 0; - ctx.namespaces = namespaces; - ctx.namespacesSize = namespaceSize; + ctx.namespaceMapping = namespaceMapping; ctx.serverUris = serverUris; ctx.serverUrisSize = serverUriSize; ctx.useReversible = useReversible; @@ -242,8 +241,7 @@ UA_NetworkMessage_encodeJson(const UA_NetworkMessage *src, ctx.calcOnly = false; if(options) { ctx.useReversible = options->useReversible; - ctx.namespacesSize = options->namespacesSize; - ctx.namespaces = options->namespaces; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUrisSize = options->serverUrisSize; ctx.serverUris = options->serverUris; ctx.prettyPrint = options->prettyPrint; @@ -265,7 +263,7 @@ UA_NetworkMessage_encodeJson(const UA_NetworkMessage *src, size_t UA_NetworkMessage_calcSizeJsonInternal(const UA_NetworkMessage *src, - UA_String *namespaces, size_t namespaceSize, + UA_NamespaceMapping *namespaceMapping, UA_String *serverUris, size_t serverUriSize, UA_Boolean useReversible) { /* Set up the context */ @@ -274,8 +272,7 @@ UA_NetworkMessage_calcSizeJsonInternal(const UA_NetworkMessage *src, ctx.pos = 0; ctx.end = (const UA_Byte*)(uintptr_t)SIZE_MAX; ctx.depth = 0; - ctx.namespaces = namespaces; - ctx.namespacesSize = namespaceSize; + ctx.namespaceMapping = namespaceMapping; ctx.serverUris = serverUris; ctx.serverUrisSize = serverUriSize; ctx.useReversible = useReversible; @@ -297,8 +294,7 @@ UA_NetworkMessage_calcSizeJson(const UA_NetworkMessage *src, ctx.calcOnly = true; if(options) { ctx.useReversible = options->useReversible; - ctx.namespacesSize = options->namespacesSize; - ctx.namespaces = options->namespaces; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUrisSize = options->serverUrisSize; ctx.serverUris = options->serverUris; ctx.prettyPrint = options->prettyPrint; diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 88e659b63..a5f195f6e 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -747,7 +747,7 @@ sendNetworkMessageJson(UA_PubSubManager *psm, UA_PubSubConnection *connection, U nm.publisherId = connection->config.publisherId; /* Compute the message length */ - size_t msgSize = UA_NetworkMessage_calcSizeJsonInternal(&nm, NULL, 0, NULL, 0, true); + size_t msgSize = UA_NetworkMessage_calcSizeJsonInternal(&nm, NULL, NULL, 0, true); UA_ConnectionManager *cm = connection->cm; if(!cm) @@ -770,7 +770,7 @@ sendNetworkMessageJson(UA_PubSubManager *psm, UA_PubSubConnection *connection, U /* Encode the message */ UA_Byte *bufPos = buf.data; const UA_Byte *bufEnd = &buf.data[msgSize]; - res = UA_NetworkMessage_encodeJsonInternal(&nm, &bufPos, &bufEnd, NULL, 0, NULL, 0, true); + res = UA_NetworkMessage_encodeJsonInternal(&nm, &bufPos, &bufEnd, NULL, NULL, 0, true); if(res != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, sendChannel, &buf); return res; diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index 87f2a4a5d..610079286 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -771,12 +771,15 @@ ENCODE_JSON(NodeId) { ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); } else { /* Check if Namespace given and in range */ - if(src->namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) { - UA_String namespaceEntry = ctx->namespaces[src->namespaceIndex]; - ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); } else { /* If not found, print the identifier */ - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); } } } @@ -841,11 +844,14 @@ ENCODE_JSON(ExpandedNodeId) { ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); } else { /* Check if Namespace given and in range */ - if(src->nodeId.namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) { - UA_String namespaceEntry = ctx->namespaces[src->nodeId.namespaceIndex]; - ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->nodeId.namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); } else { - ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); } } } @@ -901,13 +907,15 @@ ENCODE_JSON(QualifiedName) { if(src->namespaceIndex == 1) { ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); } else { - /* Check if Namespace given and in range */ - if(src->namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) { - UA_String namespaceEntry = ctx->namespaces[src->namespaceIndex]; - ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); + /* Check if Namespace given and in range */ + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); } else { - /* If not encode as number */ - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); /* If not encode as number */ } } } @@ -1307,8 +1315,7 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf, ctx.calcOnly = false; ctx.useReversible = true; /* default */ if(options) { - ctx.namespaces = options->namespaces; - ctx.namespacesSize = options->namespacesSize; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUris = options->serverUris; ctx.serverUrisSize = options->serverUrisSize; ctx.useReversible = options->useReversible; @@ -1360,8 +1367,7 @@ UA_calcSizeJson(const void *src, const UA_DataType *type, ctx.depth = 0; ctx.useReversible = true; /* default */ if(options) { - ctx.namespaces = options->namespaces; - ctx.namespacesSize = options->namespacesSize; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUris = options->serverUris; ctx.serverUrisSize = options->serverUrisSize; ctx.useReversible = options->useReversible; diff --git a/src/ua_types_encoding_json.h b/src/ua_types_encoding_json.h index 825c0da31..a252d29c5 100644 --- a/src/ua_types_encoding_json.h +++ b/src/ua_types_encoding_json.h @@ -29,8 +29,7 @@ typedef struct { UA_Boolean useReversible; UA_Boolean calcOnly; /* Only compute the length of the decoding */ - size_t namespacesSize; - const UA_String *namespaces; + UA_NamespaceMapping *namespaceMapping; size_t serverUrisSize; const UA_String *serverUris; diff --git a/tests/check_types_builtin_json.c b/tests/check_types_builtin_json.c index ef5ff2c73..576cd179c 100644 --- a/tests/check_types_builtin_json.c +++ b/tests/check_types_builtin_json.c @@ -1405,9 +1405,13 @@ START_TEST(UA_NodeId_NonReversible_Numeric_Namespace_json_encode) { const UA_DataType *type = &UA_TYPES[UA_TYPES_NODEID]; UA_String namespaces[3] = {UA_STRING("ns0"), UA_STRING("ns1"), UA_STRING("ns2")}; + UA_NamespaceMapping nsm; + memset(&nsm, 0, sizeof(UA_NamespaceMapping)); + nsm.namespaceUris = namespaces; + nsm.namespaceUrisSize = 3; + UA_EncodeJsonOptions options = {0}; - options.namespaces = namespaces; - options.namespacesSize = 3; + options.namespaceMapping = &nsm; size_t size = UA_calcSizeJson((void *) src, type, &options); ck_assert_uint_ne(size, 0); @@ -1763,10 +1767,15 @@ START_TEST(UA_QualName_NonReversible_Namespace_json_encode) { src->name = UA_STRING_ALLOC("derName"); src->namespaceIndex = 2; const UA_DataType *type = &UA_TYPES[UA_TYPES_QUALIFIEDNAME]; + + UA_NamespaceMapping nsm; + memset(&nsm, 0, sizeof(UA_NamespaceMapping)); UA_String namespaces[3] = {UA_STRING("ns0"),UA_STRING("ns1"),UA_STRING("ns2")}; + nsm.namespaceUris = namespaces; + nsm.namespaceUrisSize = 3; + UA_EncodeJsonOptions options = {0}; - options.namespaces = namespaces; - options.namespacesSize = 3; + options.namespaceMapping = &nsm; size_t size = UA_calcSizeJson((void *) src, type, &options); @@ -2708,9 +2717,13 @@ START_TEST(UA_ExpandedNodeId_NonReversible_Namespace_json_encode) { UA_String namespaces[3] = {UA_STRING("ns0"),UA_STRING("ns1"),UA_STRING("ns2")}; UA_String serverUris[3] = {UA_STRING("uri0"),UA_STRING("uri1"),UA_STRING("uri2")}; + UA_NamespaceMapping nsm; + memset(&nsm, 0, sizeof(UA_NamespaceMapping)); + nsm.namespaceUris = namespaces; + nsm.namespaceUrisSize = 3; + UA_EncodeJsonOptions options = {0}; - options.namespaces = namespaces; - options.namespacesSize = 3; + options.namespaceMapping = &nsm; options.serverUris = serverUris; options.serverUrisSize = 3; @@ -2741,9 +2754,13 @@ START_TEST(UA_ExpandedNodeId_NonReversible_NamespaceUriGiven_json_encode) { UA_String namespaces[3] = {UA_STRING("ns0"),UA_STRING("ns1"),UA_STRING("ns2")}; UA_String serverUris[3] = {UA_STRING("uri0"),UA_STRING("uri1"),UA_STRING("uri2")}; + UA_NamespaceMapping nsm; + memset(&nsm, 0, sizeof(UA_NamespaceMapping)); + nsm.namespaceUris = namespaces; + nsm.namespaceUrisSize = 3; + UA_EncodeJsonOptions options = {0}; - options.namespaces = namespaces; - options.namespacesSize = 3; + options.namespaceMapping = &nsm; options.serverUris = serverUris; options.serverUrisSize = 3; diff --git a/tests/pubsub/check_pubsub_encoding_json.c b/tests/pubsub/check_pubsub_encoding_json.c index 4a43bca6a..ccfe3afb8 100644 --- a/tests/pubsub/check_pubsub_encoding_json.c +++ b/tests/pubsub/check_pubsub_encoding_json.c @@ -81,7 +81,7 @@ START_TEST(UA_PubSub_EncodeAllOptionalFields) { UA_Variant_setScalarCopy(&m.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[0].value, &iv, &UA_TYPES[UA_TYPES_UINT32]); m.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[0].hasValue = true; - size_t size = UA_NetworkMessage_calcSizeJsonInternal(&m, NULL, 0, NULL, 0, true); + size_t size = UA_NetworkMessage_calcSizeJsonInternal(&m, NULL, NULL, 0, true); ck_assert_uint_eq(size, 340); UA_ByteString buffer; @@ -93,7 +93,7 @@ START_TEST(UA_PubSub_EncodeAllOptionalFields) { const UA_Byte *bufEnd = &(buffer.data[buffer.length]); - rv = UA_NetworkMessage_encodeJsonInternal(&m, &bufPos, &bufEnd, NULL, 0, NULL, 0, true); + rv = UA_NetworkMessage_encodeJsonInternal(&m, &bufPos, &bufEnd, NULL, NULL, 0, true); *bufPos = 0; // then ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); @@ -165,7 +165,7 @@ START_TEST(UA_PubSub_EnDecode) { UA_Variant_setScalarCopy(&m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.dataSetFields[1].value, &iv64, &UA_TYPES[UA_TYPES_INT64]); m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.dataSetFields[1].hasValue = true; - size_t size = UA_NetworkMessage_calcSizeJsonInternal(&m, NULL, 0, NULL, 0, true); + size_t size = UA_NetworkMessage_calcSizeJsonInternal(&m, NULL, NULL, 0, true); UA_ByteString buffer; UA_StatusCode rv = UA_ByteString_allocBuffer(&buffer, size); @@ -175,7 +175,7 @@ START_TEST(UA_PubSub_EnDecode) { memset(bufPos, 0, size); const UA_Byte *bufEnd = &(buffer.data[buffer.length]); - rv = UA_NetworkMessage_encodeJsonInternal(&m, &bufPos, &bufEnd, NULL, 0, NULL, 0, true); + rv = UA_NetworkMessage_encodeJsonInternal(&m, &bufPos, &bufEnd, NULL, NULL, 0, true); //*bufPos = 0; // then ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); From 44c80abd3a6388e8414a0bb4c7fa720d8956a5f8 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 7 Dec 2024 23:51:43 +0100 Subject: [PATCH 011/158] refactor(core): Use NamespaceMapping for the JSON decoding --- include/open62541/types.h | 8 ++++++-- src/pubsub/ua_pubsub_networkmessage_json.c | 3 +-- src/ua_types_encoding_json.c | 13 ++++++------- src/ua_types_encoding_json.h | 3 +-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 4ca32c7f1..2b710bbef 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1420,12 +1420,16 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf, * Zero-out the entire structure initially to ensure code-compatibility when * more fields are added in a later release. */ typedef struct { - const UA_String *namespaces; - size_t namespacesSize; + /* Mapping of namespace indices in NodeIds and of NamespaceUris in + * ExpandedNodeIds. */ + UA_NamespaceMapping *namespaceMapping; + const UA_String *serverUris; size_t serverUrisSize; + const UA_DataTypeArray *customTypes; /* Begin of a linked list with custom * datatype definitions */ + size_t *decodedLength; /* If non-NULL, the length of the decoded input is * stored to the pointer. When this is set, decoding * succeeds also if there is more content after the diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index 04354de77..544647b9e 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -558,8 +558,7 @@ UA_NetworkMessage_decodeJson(const UA_ByteString *src, memset(&ctx, 0, sizeof(ParseCtx)); ctx.tokens = tokens; if(options) { - ctx.namespacesSize = options->namespacesSize; - ctx.namespaces = options->namespaces; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUrisSize = options->serverUrisSize; ctx.serverUris = options->serverUris; ctx.customTypes = options->customTypes; diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index 610079286..539cefef2 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -1934,12 +1934,12 @@ decodeExpandedNodeIdNamespace(ParseCtx *ctx, void *dst, const UA_DataType *type) return ret; /* Replace with the index if the URI is found. Otherwise keep the string. */ - for(size_t i = 0; i < ctx->namespacesSize; i++) { - if(UA_String_equal(&en->namespaceUri, &ctx->namespaces[i])) { + if(ctx->namespaceMapping) { + UA_StatusCode mapRes = + UA_NamespaceMapping_uri2Index(ctx->namespaceMapping, en->namespaceUri, + &en->nodeId.namespaceIndex); + if(mapRes == UA_STATUSCODE_GOOD) UA_String_clear(&en->namespaceUri); - en->nodeId.namespaceIndex = (UA_UInt16)i; - break; - } } return UA_STATUSCODE_GOOD; @@ -2884,8 +2884,7 @@ UA_decodeJson(const UA_ByteString *src, void *dst, const UA_DataType *type, ctx.tokens = tokens; if(options) { - ctx.namespaces = options->namespaces; - ctx.namespacesSize = options->namespacesSize; + ctx.namespaceMapping = options->namespaceMapping; ctx.serverUris = options->serverUris; ctx.serverUrisSize = options->serverUrisSize; ctx.customTypes = options->customTypes; diff --git a/src/ua_types_encoding_json.h b/src/ua_types_encoding_json.h index a252d29c5..7ee086286 100644 --- a/src/ua_types_encoding_json.h +++ b/src/ua_types_encoding_json.h @@ -62,8 +62,7 @@ typedef struct { size_t index; UA_Byte depth; - size_t namespacesSize; - const UA_String *namespaces; + UA_NamespaceMapping *namespaceMapping; size_t serverUrisSize; const UA_String *serverUris; From 5265a58682f57b96958dd4e3babc5216fab9f9e7 Mon Sep 17 00:00:00 2001 From: Rolf Kalbermatter <15158041+RolfKal@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:37:23 +0100 Subject: [PATCH 012/158] refactor(tools): Remove unneeded header includes (#6952) --- tools/ua2json/ua2json.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/ua2json/ua2json.c b/tools/ua2json/ua2json.c index 6b4b606ab..75f26f8e8 100644 --- a/tools/ua2json/ua2json.c +++ b/tools/ua2json/ua2json.c @@ -10,12 +10,6 @@ #include #include -#if defined(_MSC_VER) -# include -typedef SSIZE_T ssize_t; -#else -#include -#endif static UA_StatusCode encode(const UA_ByteString *buf, UA_ByteString *out, const UA_DataType *type) { From 9e17e1b4f9731089a0505c09926bf534733abbc3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 12 Nov 2024 23:20:24 +0100 Subject: [PATCH 013/158] fix(plugins): Don't use the server log category for generic certificategroup functionality --- plugins/crypto/mbedtls/certificategroup.c | 8 ++++---- plugins/crypto/openssl/certificategroup.c | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index 049608283..3f2471c01 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -153,7 +153,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate mbedtls_x509_crt_init(&cert); UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &cert); if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, + UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, "An error occurred while parsing the certificate."); return retval; } @@ -161,7 +161,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate /* Check if the certificate is a CA certificate. * Only a CA certificate can have a CRL. */ if(!mbedtlsCheckCA(&cert)) { - UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, + UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate is not a CA certificate and therefore does not have a CRL."); mbedtls_x509_crt_free(&cert); return UA_STATUSCODE_GOOD; @@ -173,7 +173,7 @@ mbedtlsFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate mbedtls_x509_crl_init(&crl); retval = UA_mbedTLS_LoadCrl(&crlList[i], &crl); if(retval != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, + UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, "An error occurred while parsing the crl."); mbedtls_x509_crt_free(&cert); return retval; @@ -758,7 +758,7 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID; if(retval != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate's application URI could not be verified. StatusCode %s", UA_StatusCode_name(retval)); retval = UA_STATUSCODE_GOOD; diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index 590266730..d23fc44e7 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -159,8 +159,8 @@ openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate /* Check if the certificate is a CA certificate. * Only a CA certificate can have a CRL. */ if(!openSSLCheckCA(cert)) { - UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, - "The certificate is not a CA certificate and therefore does not have a CRL."); + UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, + "The certificate is not a CA certificate and therefore does not have a CRL."); X509_free(cert); return UA_STATUSCODE_GOOD; } @@ -811,7 +811,7 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, } if(ret != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate's application URI could not be verified. StatusCode %s", UA_StatusCode_name(ret)); ret = UA_STATUSCODE_GOOD; From d005cc1bccdf2a098733e25840503fd500a7f0f2 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 3 Dec 2024 20:25:16 +0100 Subject: [PATCH 014/158] refactor(client): Improve the logging of Endpoint selection --- src/client/ua_client_connect.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index 09273bffb..e5779dba4 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -961,7 +961,8 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne !UA_String_equal(&client->config.applicationUri, &endpoint->server.applicationUri)) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: application uri not match", i); + "Rejecting Endpoint %u: The server's ApplicationUri %S does not match " + "the client configuration", i, endpoint->server.applicationUri); return false; } @@ -970,14 +971,15 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne if(endpoint->transportProfileUri.length != 0 && !UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: transport profile does not match", i); + "Rejecting Endpoint %u: TransportProfileUri %S not supported", + i, endpoint->transportProfileUri); return false; } /* Valid SecurityMode? */ if(endpoint->securityMode < 1 || endpoint->securityMode > 3) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: invalid security mode", i); + "Rejecting Endpoint %u: Invalid SecurityMode", i); return false; } @@ -985,7 +987,7 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne if(client->config.securityMode > 0 && client->config.securityMode != endpoint->securityMode) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: security mode does not match", i); + "Rejecting Endpoint %u: SecurityMode does not match the configuration", i); return false; } @@ -993,14 +995,16 @@ matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigne if(client->config.securityPolicyUri.length > 0 && !UA_String_equal(&client->config.securityPolicyUri, &endpoint->securityPolicyUri)) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: security policy does not match the configuration", i); + "Rejecting Endpoint %u: SecurityPolicy %S does not match the configuration", + i, endpoint->securityPolicyUri); return false; } /* SecurityPolicy available? */ if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %u: security policy not available", i); + "Rejecting Endpoint %u: SecurityPolicy %S not supported", + i, endpoint->securityPolicyUri); return false; } @@ -1127,7 +1131,7 @@ responseGetEndpoints(UA_Client *client, void *userdata, * UserTokenPolicy that matches the configuration. */ if(!client->config.noSession && !findUserTokenPolicy(client, endpoint)) { UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, - "Rejecting endpoint %lu: No matching UserTokenPolicy", + "Rejecting Endpoint %lu: No matching UserTokenPolicy", (long unsigned)i); continue; } From 796a2e3a8a68a7f39d8cd96a58cd57d6cb0aec81 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 12 Nov 2024 23:23:18 +0100 Subject: [PATCH 015/158] refactor(core): Rename the log category "securitypolicy" to just "security" So it also covers the general certificate handling. --- include/open62541/plugin/log.h | 19 ++++++++++--------- plugins/ua_log_stdout.c | 2 +- plugins/ua_log_syslog.c | 2 +- tools/ua-cli/ua.c | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/open62541/plugin/log.h b/include/open62541/plugin/log.h index 1560fb197..21b685e9a 100644 --- a/include/open62541/plugin/log.h +++ b/include/open62541/plugin/log.h @@ -43,15 +43,16 @@ typedef enum { typedef enum { UA_LOGCATEGORY_NETWORK = 0, - UA_LOGCATEGORY_SECURECHANNEL, - UA_LOGCATEGORY_SESSION, - UA_LOGCATEGORY_SERVER, - UA_LOGCATEGORY_CLIENT, - UA_LOGCATEGORY_USERLAND, - UA_LOGCATEGORY_SECURITYPOLICY, - UA_LOGCATEGORY_EVENTLOOP, - UA_LOGCATEGORY_PUBSUB, - UA_LOGCATEGORY_DISCOVERY + UA_LOGCATEGORY_SECURECHANNEL = 1, + UA_LOGCATEGORY_SESSION = 2, + UA_LOGCATEGORY_SERVER = 3, + UA_LOGCATEGORY_CLIENT = 4, + UA_LOGCATEGORY_USERLAND = 5, + UA_LOGCATEGORY_SECURITYPOLICY = 6, + UA_LOGCATEGORY_SECURITY = 6, /* == SECURITYPOLICY */ + UA_LOGCATEGORY_EVENTLOOP = 7, + UA_LOGCATEGORY_PUBSUB = 8, + UA_LOGCATEGORY_DISCOVERY = 9 } UA_LogCategory; typedef struct UA_Logger { diff --git a/plugins/ua_log_stdout.c b/plugins/ua_log_stdout.c index d5a9246be..787fcd841 100644 --- a/plugins/ua_log_stdout.c +++ b/plugins/ua_log_stdout.c @@ -42,7 +42,7 @@ const char *logLevelNames[6] = {"trace", "debug", static const char * logCategoryNames[UA_LOGCATEGORIES] = {"network", "channel", "session", "server", "client", - "userland", "securitypolicy", "eventloop", "pubsub", "discovery"}; + "userland", "security", "eventloop", "pubsub", "discovery"}; /* Protect crosstalk during logging via global lock. * Use a spinlock on non-POSIX as we cannot statically initialize a global lock. */ diff --git a/plugins/ua_log_syslog.c b/plugins/ua_log_syslog.c index 7f6efcb5e..03452a1e3 100644 --- a/plugins/ua_log_syslog.c +++ b/plugins/ua_log_syslog.c @@ -17,7 +17,7 @@ const char *syslogLevelNames[6] = {"trace", "debug", "info", "warn", "error", "fatal"}; const char *syslogCategoryNames[UA_LOGCATEGORIES] = {"network", "channel", "session", "server", "client", - "userland", "securitypolicy", "eventloop", "pubsub", "discovery"}; + "userland", "security", "eventloop", "pubsub", "discovery"}; #ifdef __clang__ __attribute__((__format__(__printf__, 4 , 0))) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 507301f8b..73a4d595e 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -47,7 +47,7 @@ logLevelNames[6] = {"trace", "debug", ANSI_COLOR_GREEN "info", static const char * logCategoryNames[UA_LOGCATEGORIES] = {"network", "channel", "session", "server", "client", - "userland", "securitypolicy", "eventloop", "pubsub", "discovery"}; + "userland", "security", "eventloop", "pubsub", "discovery"}; static void cliLog(void *context, UA_LogLevel level, UA_LogCategory category, From 9f6b89b4cbcc7a4a520c78fcdd54a1bcc76c711b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 12 Nov 2024 23:32:44 +0100 Subject: [PATCH 016/158] feat(plugins): Use the application's logger in UA_CertificateUtils_verifyApplicationURI --- include/open62541/plugin/certificategroup.h | 3 ++- plugins/crypto/mbedtls/certificategroup.c | 6 +++--- plugins/crypto/openssl/certificategroup.c | 6 +++--- plugins/crypto/ua_certificategroup_none.c | 3 ++- src/client/ua_client_connect.c | 6 ++++-- src/server/ua_server.c | 3 ++- src/server/ua_services_session.c | 3 ++- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/open62541/plugin/certificategroup.h b/include/open62541/plugin/certificategroup.h index 0e074cace..b2c1ac66f 100644 --- a/include/open62541/plugin/certificategroup.h +++ b/include/open62541/plugin/certificategroup.h @@ -63,7 +63,8 @@ struct UA_CertificateGroup { UA_EXPORT UA_StatusCode UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, const UA_ByteString *certificate, - const UA_String *applicationURI); + const UA_String *applicationURI, + UA_Logger *logger); /* Get the expire date from certificate */ UA_EXPORT UA_StatusCode diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index 3f2471c01..10f7f72c1 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -9,7 +9,6 @@ #include #include -#include #ifdef UA_ENABLE_ENCRYPTION_MBEDTLS @@ -739,7 +738,8 @@ UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l UA_StatusCode UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, const UA_ByteString *certificate, - const UA_String *applicationURI) { + const UA_String *applicationURI, + UA_Logger *logger) { /* Parse the certificate */ mbedtls_x509_crt remoteCertificate; mbedtls_x509_crt_init(&remoteCertificate); @@ -758,7 +758,7 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID; if(retval != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate's application URI could not be verified. StatusCode %s", UA_StatusCode_name(retval)); retval = UA_STATUSCODE_GOOD; diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index d23fc44e7..a677c0993 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -9,7 +9,6 @@ #include #include -#include #if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || defined(UA_ENABLE_ENCRYPTION_LIBRESSL) #include @@ -761,7 +760,8 @@ cleanup: UA_StatusCode UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, const UA_ByteString *certificate, - const UA_String *applicationURI) { + const UA_String *applicationURI, + UA_Logger *logger) { const unsigned char * pData; X509 * certificateX509; UA_String subjectURI = UA_STRING_NULL; @@ -811,7 +811,7 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, } if(ret != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate's application URI could not be verified. StatusCode %s", UA_StatusCode_name(ret)); ret = UA_STATUSCODE_GOOD; diff --git a/plugins/crypto/ua_certificategroup_none.c b/plugins/crypto/ua_certificategroup_none.c index 8212700c5..64b560265 100644 --- a/plugins/crypto/ua_certificategroup_none.c +++ b/plugins/crypto/ua_certificategroup_none.c @@ -40,7 +40,8 @@ void UA_CertificateGroup_AcceptAll(UA_CertificateGroup *certGroup) { UA_StatusCode UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, const UA_ByteString *certificate, - const UA_String *applicationURI){ + const UA_String *applicationURI, + UA_Logger *logger) { return UA_STATUSCODE_GOOD; } diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index e5779dba4..2cf1f40fb 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -1610,8 +1610,10 @@ verifyClientApplicationURI(const UA_Client *client) { } UA_StatusCode retval = - UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris, &sp->localCertificate, - &client->config.clientDescription.applicationUri); + UA_CertificateUtils_verifyApplicationURI(client->allowAllCertificateUris, + &sp->localCertificate, + &client->config.clientDescription.applicationUri, + client->config.logging); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, "The configured ApplicationURI does not match the URI " diff --git a/src/server/ua_server.c b/src/server/ua_server.c index 8475dc8a9..26d8238f7 100644 --- a/src/server/ua_server.c +++ b/src/server/ua_server.c @@ -1035,7 +1035,8 @@ verifyServerApplicationURI(const UA_Server *server) { UA_StatusCode retval = UA_CertificateUtils_verifyApplicationURI(server->config.allowAllCertificateUris, &sp->localCertificate, - &server->config.applicationDescription.applicationUri); + &server->config.applicationDescription.applicationUri, + server->config.logging); UA_CHECK_STATUS_ERROR(retval, return retval, server->config.logging, UA_LOGCATEGORY_SERVER, "The configured ApplicationURI \"%S\" does not match the " diff --git a/src/server/ua_services_session.c b/src/server/ua_services_session.c index c9dd4e7bc..1a46a1403 100644 --- a/src/server/ua_services_session.c +++ b/src/server/ua_services_session.c @@ -308,7 +308,8 @@ Service_CreateSession(UA_Server *server, UA_SecureChannel *channel, response->responseHeader.serviceResult = UA_CertificateUtils_verifyApplicationURI(server->config.allowAllCertificateUris, &request->clientCertificate, - &request->clientDescription.applicationUri); + &request->clientDescription.applicationUri, + server->config.logging); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(server->config.logging, channel, "The client's ApplicationURI did not match the certificate"); From 41f7dc4f878b50c7592e9ca7c276aeaff4093419 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 9 Dec 2024 20:15:05 +0100 Subject: [PATCH 017/158] refactor(plugins): Improve the logging of mismatching ApplicationURI in openssl/certificategroup.c --- plugins/crypto/openssl/certificategroup.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index a677c0993..4154e191b 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -810,13 +810,16 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID; } - if(ret != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { + if(ret != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) { UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, - "The certificate's application URI could not be verified. StatusCode %s", - UA_StatusCode_name(ret)); - ret = UA_STATUSCODE_GOOD; + "The certificate's Subject Alternative Name URI (%S) " + "does not match the ApplicationURI (%S)", + subjectURI, *applicationURI); } + if(ruleHandling != UA_RULEHANDLING_ABORT) + ret = UA_STATUSCODE_GOOD; + X509_free (certificateX509); sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); UA_String_clear (&subjectURI); From 19157ff85e7f7df781d93b018ded89e6c1450fe0 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 9 Dec 2024 20:16:02 +0100 Subject: [PATCH 018/158] refactor(tools): Correctly filter by the loglevel in the ua-cli tool --- tools/ua-cli/ua.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 73a4d595e..7c68ea3c2 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -51,14 +51,15 @@ logCategoryNames[UA_LOGCATEGORIES] = static void cliLog(void *context, UA_LogLevel level, UA_LogCategory category, - const char *msg, va_list args) { - if(logLevel > level) - return; + const char *msg, va_list args) { /* Set to fatal if the level is outside the range */ - int logLevelSlot = ((int)level / 100) - 1; - if(logLevelSlot < 0 || logLevelSlot > 5) - logLevelSlot = 5; + int l = ((int)level / 100) - 1; + if(l < 0 || l > 5) + l = 5; + + if((int)logLevel - 1 > l) + return; /* Log */ #define LOGBUFSIZE 512 @@ -66,7 +67,7 @@ cliLog(void *context, UA_LogLevel level, UA_LogCategory category, UA_String out = {LOGBUFSIZE, logbuf}; UA_String_vprintf(&out, msg, args); fprintf(stderr, "%s/%s" ANSI_COLOR_RESET "\t", - logLevelNames[logLevelSlot], logCategoryNames[category]); + logLevelNames[l], logCategoryNames[category]); fprintf(stderr, "%s\n", logbuf); fflush(stderr); } From 9ae8b76986ba72a7c04bf0d7520acd46f5c01159 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 07:02:30 +0100 Subject: [PATCH 019/158] refactor(plugins): Use urn:open62541.unconfigured.application as the default ApplicationUri We want to have the same default for client and server. So we get less trouble with reusing certificates. Before we had to manually adjust the client URI to say "server". That's not good... --- .../client_access_control_encrypt.c | 2 -- examples/client_connect.c | 7 ------- examples/encryption/README_client_server_tpm_keystore.txt | 4 ++-- examples/encryption/client_encryption.c | 6 ++---- examples/encryption/server_encryption.c | 2 +- examples/pubsub/sks/server_pubsub_central_sks.c | 5 ----- plugins/ua_config_default.c | 5 ++--- tests/client/check_client_authentication.c | 8 -------- tests/encryption/check_cert_generation.c | 2 +- tests/encryption/check_update_certificate.c | 2 +- tests/encryption/check_update_trustlist.c | 2 +- tools/certs/create_self-signed.py | 2 +- 12 files changed, 11 insertions(+), 36 deletions(-) diff --git a/examples/access_control_encrypt/client_access_control_encrypt.c b/examples/access_control_encrypt/client_access_control_encrypt.c index d4bb10d1b..6a6817950 100644 --- a/examples/access_control_encrypt/client_access_control_encrypt.c +++ b/examples/access_control_encrypt/client_access_control_encrypt.c @@ -46,8 +46,6 @@ int main(int argc, char* argv[]) { UA_Client *client = UA_Client_new(); UA_ClientConfig *config = UA_Client_getConfig(client); config->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - UA_String_clear(&config->clientDescription.applicationUri); - config->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application"); UA_ClientConfig_setDefaultEncryption(config, certificate, privateKey, trustList, trustListSize, revocationList, revocationListSize); diff --git a/examples/client_connect.c b/examples/client_connect.c index c533ec7f9..09d370a9a 100644 --- a/examples/client_connect.c +++ b/examples/client_connect.c @@ -153,13 +153,6 @@ int main(int argc, char *argv[]) { UA_ClientConfig_setDefault(cc); #endif - /* The application URI must be the same as the one in the certificate. - * The script for creating a self-created certificate generates a certificate - * with the Uri specified below.*/ - UA_ApplicationDescription_clear(&cc->clientDescription); - cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application"); - cc->clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT; - /* Connect to the server */ UA_StatusCode retval = UA_STATUSCODE_GOOD; if(username) { diff --git a/examples/encryption/README_client_server_tpm_keystore.txt b/examples/encryption/README_client_server_tpm_keystore.txt index c448453f7..c810768de 100644 --- a/examples/encryption/README_client_server_tpm_keystore.txt +++ b/examples/encryption/README_client_server_tpm_keystore.txt @@ -88,10 +88,10 @@ Create encryption and signing key in both the server and client node filesystems cd open62541/tools/tpm_keystore/ In server, - python3 ../certs/create_self-signed.py -u urn:open62541.server.application + python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c server In client, - python3 ../certs/create_self-signed.py -u urn:unconfigured:application -c client + python3 ../certs/create_self-signed.py -u urn:open62541.unconfigured.application -c client Seal the encryption and signing key files using the key available in TPM gcc cert_encrypt_tpm.c -o cert_encrypt_tpm -ltpm2_pkcs11 -lssl -lcrypto diff --git a/examples/encryption/client_encryption.c b/examples/encryption/client_encryption.c index 07ecab81c..0c12e9c12 100644 --- a/examples/encryption/client_encryption.c +++ b/examples/encryption/client_encryption.c @@ -45,11 +45,9 @@ int main(int argc, char* argv[]) { UA_Client *client = UA_Client_new(); UA_ClientConfig *cc = UA_Client_getConfig(client); cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - UA_String_clear(&cc->clientDescription.applicationUri); - cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application"); UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, - trustList, trustListSize, - revocationList, revocationListSize); + trustList, trustListSize, + revocationList, revocationListSize); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Failed to set encryption." ); diff --git a/examples/encryption/server_encryption.c b/examples/encryption/server_encryption.c index 57b2db424..fbdeeae67 100644 --- a/examples/encryption/server_encryption.c +++ b/examples/encryption/server_encryption.c @@ -46,7 +46,7 @@ int main(int argc, char* argv[]) { UA_UInt32 lenSubject = 3; UA_String subjectAltName[2]= { UA_STRING_STATIC("DNS:localhost"), - UA_STRING_STATIC("URI:urn:open62541.server.application") + UA_STRING_STATIC("URI:urn:open62541.unconfigured.application") }; UA_UInt32 lenSubjectAltName = 2; UA_KeyValueMap *kvm = UA_KeyValueMap_new(); diff --git a/examples/pubsub/sks/server_pubsub_central_sks.c b/examples/pubsub/sks/server_pubsub_central_sks.c index 7a4fcd6f1..795204b73 100644 --- a/examples/pubsub/sks/server_pubsub_central_sks.c +++ b/examples/pubsub/sks/server_pubsub_central_sks.c @@ -428,11 +428,6 @@ main(int argc, char **argv) { if(enableTime) config.verifyRequestTimestamp = UA_RULEHANDLING_DEFAULT; - /* Override with a custom access control policy */ - UA_String_clear(&config.applicationDescription.applicationUri); - config.applicationDescription.applicationUri = - UA_String_fromChars("urn:open62541.server.application"); - config.shutdownDelay = 5000.0; /* 5s */ /* Add supported pubsub security policies by this sks instance */ diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index 941da318e..0d593903a 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -207,8 +207,7 @@ const UA_ConnectionConfig UA_ConnectionConfig_default = { #define PRODUCT_NAME "open62541 OPC UA Server" #define PRODUCT_URI "http://open62541.org" #define APPLICATION_NAME "open62541-based OPC UA Application" -#define APPLICATION_URI "urn:unconfigured:application" -#define APPLICATION_URI_SERVER "urn:open62541.server.application" +#define APPLICATION_URI "urn:open62541.unconfigured.application" #define SECURITY_POLICY_SIZE 7 @@ -368,7 +367,7 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) { conf->buildInfo.buildDate = UA_DateTime_now(); UA_ApplicationDescription_clear(&conf->applicationDescription); - conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI_SERVER); + conf->applicationDescription.applicationUri = UA_STRING_ALLOC(APPLICATION_URI); conf->applicationDescription.productUri = UA_STRING_ALLOC(PRODUCT_URI); conf->applicationDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC("en", APPLICATION_NAME); diff --git a/tests/client/check_client_authentication.c b/tests/client/check_client_authentication.c index df19f8863..8f3fb64cd 100644 --- a/tests/client/check_client_authentication.c +++ b/tests/client/check_client_authentication.c @@ -112,10 +112,6 @@ START_TEST(Client_connect_certificate) { NULL, 0, NULL, 0); UA_CertificateGroup_AcceptAll(&cc->certificateVerification); - /* Set the ApplicationUri used in the certificate */ - UA_String_clear(&cc->clientDescription.applicationUri); - cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application"); - UA_ClientConfig_setAuthenticationCert(cc, certificateAuth, privateKeyAuth); UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); @@ -156,10 +152,6 @@ START_TEST(Client_connect_invalid_certificate) { NULL, 0, NULL, 0); UA_CertificateGroup_AcceptAll(&cc->certificateVerification); - /* Set the ApplicationUri used in the certificate */ - UA_String_clear(&cc->clientDescription.applicationUri); - cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application"); - UA_ClientConfig_setAuthenticationCert(cc, certificateAuth, privateKeyAuth); UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); diff --git a/tests/encryption/check_cert_generation.c b/tests/encryption/check_cert_generation.c index a132f3698..6274fb8a9 100644 --- a/tests/encryption/check_cert_generation.c +++ b/tests/encryption/check_cert_generation.c @@ -32,7 +32,7 @@ START_TEST(certificate_generation) { UA_UInt32 lenSubject = 3; UA_String subjectAltName[2]= { UA_STRING_STATIC("DNS:localhost"), - UA_STRING_STATIC("URI:urn:open62541.server.application") + UA_STRING_STATIC("URI:urn:open62541.unconfigured.application") }; UA_UInt32 lenSubjectAltName = 2; UA_KeyValueMap *kvm = UA_KeyValueMap_new(); diff --git a/tests/encryption/check_update_certificate.c b/tests/encryption/check_update_certificate.c index c58fddd7d..67334674a 100644 --- a/tests/encryption/check_update_certificate.c +++ b/tests/encryption/check_update_certificate.c @@ -71,7 +71,7 @@ static void generateCertificate(UA_ByteString *certificate, UA_ByteString *privK UA_UInt32 lenSubject = 3; UA_String subjectAltName[2]= { UA_STRING_STATIC("DNS:localhost"), - UA_STRING_STATIC("URI:urn:open62541.server.application") + UA_STRING_STATIC("URI:urn:open62541.unconfigured.application") }; UA_UInt32 lenSubjectAltName = 2; UA_KeyValueMap *kvm = UA_KeyValueMap_new(); diff --git a/tests/encryption/check_update_trustlist.c b/tests/encryption/check_update_trustlist.c index 8b2b1f75b..24c2b195e 100644 --- a/tests/encryption/check_update_trustlist.c +++ b/tests/encryption/check_update_trustlist.c @@ -71,7 +71,7 @@ static void generateCertificate(UA_ByteString *certificate, UA_ByteString *privK UA_UInt32 lenSubject = 3; UA_String subjectAltName[2]= { UA_STRING_STATIC("DNS:localhost"), - UA_STRING_STATIC("URI:urn:open62541.server.application") + UA_STRING_STATIC("URI:urn:open62541.unconfigured.application") }; UA_UInt32 lenSubjectAltName = 2; UA_KeyValueMap *kvm = UA_KeyValueMap_new(); diff --git a/tools/certs/create_self-signed.py b/tools/certs/create_self-signed.py index c284deefc..9e2749f19 100755 --- a/tools/certs/create_self-signed.py +++ b/tools/certs/create_self-signed.py @@ -48,7 +48,7 @@ if args.keysize: keysize = args.keysize if args.uri == "": - args.uri = "urn:open62541.server.application" + args.uri = "urn:open62541.unconfigured.application" print("No ApplicationUri given for the certificate. Setting to %s" % args.uri) os.environ['URI1'] = args.uri From 6c5a3cb2427f92f45e606f5739a5c3e3d359b535 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 30 Nov 2024 23:30:55 +0100 Subject: [PATCH 020/158] refactor(build): Use the POSIX architecture as default --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e963f81c4..c19880926 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,11 +71,11 @@ set(UA_ARCHITECTURE "" CACHE STRING "Architecture to build open62541 for") SET_PROPERTY(CACHE UA_ARCHITECTURE PROPERTY STRINGS "" ${UA_ARCHITECTURES}) if("${UA_ARCHITECTURE}" STREQUAL "") - if(UNIX) - set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE) - elseif(WIN32) + if(WIN32) set(UA_ARCHITECTURE "win32" CACHE STRING "" FORCE) - endif(UNIX) + else() + set(UA_ARCHITECTURE "posix" CACHE STRING "" FORCE) + endif() endif() message(STATUS "The selected architecture is: ${UA_ARCHITECTURE}") From 39ef16fb67e01e69ad81a161e3f4c5144bac2532 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 3 Dec 2024 20:24:12 +0100 Subject: [PATCH 021/158] fix(arch): Make the POSIX EventLoop compile against the cosmopolitan libc --- arch/posix/eventloop_posix.h | 1 + arch/posix/eventloop_posix_udp.c | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/arch/posix/eventloop_posix.h b/arch/posix/eventloop_posix.h index 4c201a28c..3dcaebdc8 100644 --- a/arch/posix/eventloop_posix.h +++ b/arch/posix/eventloop_posix.h @@ -122,6 +122,7 @@ typedef SSIZE_T ssize_t; /* POSIX Definitions */ /*********************/ +#include #include #include #include diff --git a/arch/posix/eventloop_posix_udp.c b/arch/posix/eventloop_posix_udp.c index 023242bf6..b439bb4c3 100644 --- a/arch/posix/eventloop_posix_udp.c +++ b/arch/posix/eventloop_posix_udp.c @@ -72,7 +72,7 @@ typedef enum { } MultiCastType; typedef union { -#ifdef _WIN32 +#if !defined(ip_mreqn) struct ip_mreq ipv4; #else struct ip_mreqn ipv4; @@ -197,9 +197,11 @@ setMulticastInterface(const char *netif, struct addrinfo *info, if(ifa->ifa_addr->sa_family != info->ai_family) continue; +#if defined(_WIN32) || defined(ip_mreqn) idx = UA_if_nametoindex(ifa->ifa_name); if(idx == 0) continue; +#endif /* Found network interface by name */ if(strcmp(ifa->ifa_name, netif) == 0) @@ -228,12 +230,15 @@ setMulticastInterface(const char *netif, struct addrinfo *info, return UA_STATUSCODE_BADINTERNALERROR; /* Write the interface index */ - if(info->ai_family == AF_INET) + if(info->ai_family == AF_INET) { +#if defined(ip_mreqn) req->ipv4.imr_ifindex = idx; +#endif #if UA_IPV6 - else /* if(info->ai_family == AF_INET6) */ + } else { /* if(info->ai_family == AF_INET6) */ req->ipv6.ipv6mr_interface = idx; #endif + } return UA_STATUSCODE_GOOD; } @@ -246,7 +251,7 @@ setupMulticastRequest(UA_FD socket, MulticastRequest *req, const UA_KeyValueMap if(info->ai_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr; req->ipv4.imr_multiaddr = sin->sin_addr; -#ifdef _WIN32 +#if !defined(ip_mreqn) req->ipv4.imr_interface.s_addr = htonl(INADDR_ANY); /* default ANY */ #else req->ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */ From c8146660a43e0ea9876f9af0831aa0121eafb117 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 07:04:24 +0100 Subject: [PATCH 022/158] refactor(examples): Use the AcceptAll certificate verification for the examples Don't require the examples to have the remote certificate in a trustlist. This reduces initial confusion and still logs a warning. --- examples/encryption/server_encryption.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/encryption/server_encryption.c b/examples/encryption/server_encryption.c index fbdeeae67..f65a0c2f5 100644 --- a/examples/encryption/server_encryption.c +++ b/examples/encryption/server_encryption.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -93,6 +94,13 @@ int main(int argc, char* argv[]) { issuerList, issuerListSize, revocationList, revocationListSize); + /* Accept all certificates */ + config->secureChannelPKI.clear(&config->secureChannelPKI); + UA_CertificateGroup_AcceptAll(&config->secureChannelPKI); + + config->sessionPKI.clear(&config->sessionPKI); + UA_CertificateGroup_AcceptAll(&config->sessionPKI); + UA_ByteString_clear(&certificate); UA_ByteString_clear(&privateKey); for(size_t i = 0; i < trustListSize; i++) From 980f96a9d386b5cef6b4a091631efbd8d1b3a73e Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 07:06:48 +0100 Subject: [PATCH 023/158] refactor(examples): Move the client_access_control_encrypt.c example to the correct folder --- examples/CMakeLists.txt | 2 +- .../client_access_control_encrypt.c | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{access_control_encrypt => access_control}/client_access_control_encrypt.c (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1c080334a..afff4186d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -196,7 +196,7 @@ if(UA_ENABLE_NODEMANAGEMENT) add_example(access_control_server access_control/server_access_control.c) add_example(access_control_client access_control/client_access_control.c) if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") - add_example(access_control_client_encrypt access_control_encrypt/client_access_control_encrypt.c) + add_example(access_control_client_encrypt access_control/client_access_control_encrypt.c) endif() endif() diff --git a/examples/access_control_encrypt/client_access_control_encrypt.c b/examples/access_control/client_access_control_encrypt.c similarity index 100% rename from examples/access_control_encrypt/client_access_control_encrypt.c rename to examples/access_control/client_access_control_encrypt.c From 52b18e6f8de72de1a260c74b2ad2c6aa627fc189 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 07:29:30 +0100 Subject: [PATCH 024/158] refactor(examples): Move server_file_based_config.c -> server_json_config.c --- examples/CMakeLists.txt | 2 +- .../server_file_based_config.c => server_json_config.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{file_based_server/server_file_based_config.c => server_json_config.c} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index afff4186d..b7c891de6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -163,7 +163,7 @@ add_example(server_inheritance server_inheritance.c) add_example(server_loglevel server_loglevel.c) if(UA_ENABLE_JSON_ENCODING) - add_example(server_file_based_config file_based_server/server_file_based_config.c) + add_example(server_json_config server_json_config.c) endif() if(UA_ENABLE_HISTORIZING) diff --git a/examples/file_based_server/server_file_based_config.c b/examples/server_json_config.c similarity index 100% rename from examples/file_based_server/server_file_based_config.c rename to examples/server_json_config.c From 597a9c641a55f116d2ee18912d59ed943c6bbbe3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 07:34:39 +0100 Subject: [PATCH 025/158] refactor(examples): Remove outdated eventfilter query examples --- examples/CMakeLists.txt | 7 -- examples/events/client_filter_queries.c | 100 ++---------------- .../events/eventfilter_parser_examples.h.in | 14 --- examples/events/example_queries/case_0.txt | 11 -- examples/events/example_queries/case_1.txt | 24 ----- examples/events/example_queries/case_2.txt | 86 --------------- examples/events/example_queries/case_3.txt | 17 --- examples/events/example_queries/case_4.txt | 17 --- 8 files changed, 8 insertions(+), 268 deletions(-) delete mode 100644 examples/events/eventfilter_parser_examples.h.in delete mode 100644 examples/events/example_queries/case_0.txt delete mode 100644 examples/events/example_queries/case_1.txt delete mode 100644 examples/events/example_queries/case_2.txt delete mode 100644 examples/events/example_queries/case_3.txt delete mode 100644 examples/events/example_queries/case_4.txt diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b7c891de6..62b80f795 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -103,13 +103,6 @@ if(UA_ENABLE_SUBSCRIPTIONS_EVENTS) add_example(client_event_filter events/client_eventfilter.c) if(UA_ENABLE_PARSING) add_example(client_event_filter_queries events/client_filter_queries.c) - file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_1.txt CASE_1 NEWLINE_CONSUME) - file(STRINGS ${PROJECT_SOURCE_DIR}/events/example_queries/case_2.txt CASE_2 NEWLINE_CONSUME) - STRING(REGEX REPLACE "\n" " " CASE_1 ${CASE_1}) - STRING(REGEX REPLACE "\"" "\\\\\"" CASE_1 "${CASE_1}") - STRING(REGEX REPLACE "\n" " " CASE_2 ${CASE_2}) - STRING(REGEX REPLACE "\"" "\\\\\"" CASE_2 "${CASE_2}") - configure_file(${PROJECT_SOURCE_DIR}/events/eventfilter_parser_examples.h.in ${PROJECT_BINARY_DIR}/../src_generated/open62541/eventfilter_parser_examples.h @ONLY) endif() if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c) diff --git a/examples/events/client_filter_queries.c b/examples/events/client_filter_queries.c index 342a3badd..94f6d8a81 100644 --- a/examples/events/client_filter_queries.c +++ b/examples/events/client_filter_queries.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -18,94 +17,9 @@ * This Tutorial repeats the client_eventfilter.c tutorial, * however the filter are created based on the Query Language for Eventfilter */ -static void -check_eventfilter(UA_EventFilter *filter, UA_Boolean print_filter){ - UA_EventFilter empty_filter; - UA_EventFilter_init(&empty_filter); - if(memcmp(&empty_filter, filter, sizeof(UA_EventFilter)) == 0){ - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Failed to parse the EventFilter"); - } - else{ - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "EventFilter parsing succeeded"); - if(print_filter){ - UA_String out = UA_STRING_NULL; - UA_print(filter, &UA_TYPES[UA_TYPES_EVENTFILTER], &out); - printf("%.*s\n", (int)out.length, out.data); - UA_String_clear(&out); - } - } -} static UA_Boolean running = true; -static UA_StatusCode -read_queries(UA_UInt16 filterSelection, UA_EventFilter *filter){ - switch(filterSelection){ - case 0 : { - char *inp = "SELECT /Message, /0:Severity /EventType " - "WHERE OR($ref_1, $ref_2) " - "FOR " - "$ref2 := OFTYPE(ns=1;i=5003) AND " - "$ref1 := (OFTYPE i=3035)"; - - UA_ByteString case_0 = UA_String_fromChars(inp); - UA_EventFilter_parse(filter, case_0, NULL); - check_eventfilter(filter, UA_FALSE); - UA_ByteString_clear(&case_0); - break; - } - case 1 : { - /*query can be found in the file example_queries/case_1 */ - UA_ByteString case_1 = UA_String_fromChars(CASE_1); - UA_EventFilter_parse(filter, case_1, NULL); - check_eventfilter(filter, UA_FALSE); - UA_ByteString_clear(&case_1); - break; - } - case 2 : { - /*query can be found in the file example_queries/case_2 */ - UA_ByteString case_2 = UA_String_fromChars(CASE_2); - UA_EventFilter_parse(filter, case_2, NULL); - check_eventfilter(filter, UA_FALSE); - UA_ByteString_clear(&case_2); - break; - } - case 3 : { - char *inp = "SELECT /Message, /Severity, /EventType " - "WHERE AND(OFTYPE(ns=1;i=5001), $abc) " - "FOR " - "$abc := AND($xyz, $uvw) AND " - "$xyz := ==(INT64 99, 99) AND " - "$uvw := GREATER(i=5000/Severity, 99)"; - - UA_ByteString case_3 = UA_String_fromChars(inp); - UA_EventFilter_parse(filter, case_3, NULL); - check_eventfilter(filter, UA_FALSE); - UA_ByteString_clear(&case_3); - break; - } - case 4 : { - char *inp = "SELECT /Message, /0:Severity, /EventType " - "WHERE " - "AND($a, GREATER(i=5000/Severity, $ref)) " - "FOR " - "$ref := 99 AND " - "$a := OFTYPE(ns=1;i=5000)"; - UA_ByteString case_4 = UA_String_fromChars(inp); - UA_EventFilter_parse(filter, case_4, NULL); - check_eventfilter(filter, UA_FALSE); - UA_ByteString_clear(&case_4); - break; - } - default: - UA_EventFilter_clear(filter); - return UA_STATUSCODE_BADCONFIGURATIONERROR; - } - return UA_STATUSCODE_GOOD; -} - static void handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, @@ -139,14 +53,16 @@ handler_events_filter(UA_Client *client, UA_UInt32 subId, void *subContext, } } - static UA_StatusCode -create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *client, +create_event_filter_with_monitored_item(UA_Client *client, UA_EventFilter *filter, UA_CreateSubscriptionResponse *response, UA_MonitoredItemCreateResult *result){ /* read the eventfilter query string and create the corresponding eventfilter */ - UA_StatusCode retval = read_queries(filterSelection, filter); + + char *input = "SELECT /Message, /0:Severity, /EventType " + "WHERE /Severity >= 100"; + UA_StatusCode retval = UA_EventFilter_parse(filter, UA_STRING(input), NULL); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Failed to parse the filter query with statuscode %s \n", @@ -187,8 +103,8 @@ create_event_filter_with_monitored_item(UA_UInt16 filterSelection, UA_Client *cl return UA_STATUSCODE_BAD; } UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Monitoring 'Root->Objects->Server', id %u", - response->subscriptionId); + "Monitoring 'Root/Objects/Server', id %u", + response->subscriptionId); monId = result->monitoredItemId; return UA_STATUSCODE_GOOD; } @@ -219,7 +135,7 @@ int main(int argc, char *argv[]) { UA_EventFilter_init(&filter); UA_CreateSubscriptionResponse *response = UA_CreateSubscriptionResponse_new(); UA_MonitoredItemCreateResult *result = UA_MonitoredItemCreateResult_new(); - retval = create_event_filter_with_monitored_item(3, client, &filter, response, result); + retval = create_event_filter_with_monitored_item(client, &filter, response, result); if(retval == UA_STATUSCODE_GOOD){ while(running) UA_Client_run_iterate(client, true); diff --git a/examples/events/eventfilter_parser_examples.h.in b/examples/events/eventfilter_parser_examples.h.in deleted file mode 100644 index 726829a7f..000000000 --- a/examples/events/eventfilter_parser_examples.h.in +++ /dev/null @@ -1,14 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright 2023-2024 (c) Fraunhofer IOSB (Author: Florian Düwel) - */ -#ifndef VERSION_H_IN -#define VERSION_H_IN - - -#define CASE_1 "@CASE_1@" -#define CASE_2 "@CASE_2@" - -#endif // VERSION_H_IN diff --git a/examples/events/example_queries/case_0.txt b/examples/events/example_queries/case_0.txt deleted file mode 100644 index 2df3422f7..000000000 --- a/examples/events/example_queries/case_0.txt +++ /dev/null @@ -1,11 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - See http://creativecommons.org/publicdomain/zero/1.0/ for more information. -*/ - -SELECT -PATH "/Message", PATH "/0:Severity", PATH "/EventType" -WHERE -OR($"ref_1", $"ref_2") -FOR -$"ref_2":= OFTYPE ns=1;i=5003 -$"ref_1":= OFTYPE i=3035 \ No newline at end of file diff --git a/examples/events/example_queries/case_1.txt b/examples/events/example_queries/case_1.txt deleted file mode 100644 index 12d61b415..000000000 --- a/examples/events/example_queries/case_1.txt +++ /dev/null @@ -1,24 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - See http://creativecommons.org/publicdomain/zero/1.0/ for more information. -*/ - -SELECT - -PATH "/Message", -PATH "/Severity", -PATH "/EventType" - -WHERE -OFTYPE ns=1;i=5001 - -/*alternative queries to create an identical eventfilter - -1. -SELECT PATH "/Message", PATH "/Severity", PATH "/EventType" -WHERE -$4 -FOR -$4:= OFTYPE ns=1;i=5001 - - -*/ \ No newline at end of file diff --git a/examples/events/example_queries/case_2.txt b/examples/events/example_queries/case_2.txt deleted file mode 100644 index 66b2d646f..000000000 --- a/examples/events/example_queries/case_2.txt +++ /dev/null @@ -1,86 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - See http://creativecommons.org/publicdomain/zero/1.0/ for more information. -*/ - -SELECT - -PATH "/Message", PATH "/Severity", PATH "/EventType" - -WHERE -OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2)) - -FOR -$1:= OFTYPE $7 -$2:= OFTYPE $8 -$4:= OFTYPE ns=1;i=5003 -$5:= OFTYPE ns=1;i=5004 -$7:= NODEID ns=1;i=5000 -$8:= ns=1;i=5001 - - - -/* alternative queries to create an identical eventfilter -1. -SELECT -PATH "/Message", PATH "/Severity", PATH "/EventType" -WHERE -OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001)) - -2. -SELECT -PATH "/Message", PATH "/Severity", PATH "/EventType" -WHERE -OR(OR(OR($3, $4), OR($5, $6)), OR($1,$2)) -FOR -$1:= OFTYPE ns=1;i=5000 -$2:= OFTYPE ns=1;i=5001 -$3:= OFTYPE ns=1;i=5002 -$4:= OFTYPE ns=1;i=5003 -$5:= OFTYPE ns=1;i=5004 -$6:= OFTYPE i=3035 - -3. -SELECT -PATH "/Message", PATH "/Severity", PATH "/EventType" -WHERE -$"test" -FOR -$"test":= OR(OR(OR(OFTYPE ns=1;i=5002, OFTYPE ns=1;i=5003), OR(OFTYPE ns=1;i=5004, OFTYPE ns=0;i=3035)), OR(OFTYPE ns=1;i=5000, OFTYPE ns=1;i=5001)) - -4. -SELECT -PATH "/Message", PATH "/Severity", PATH "/EventType" -WHERE -OR($"first branch", $"second branch") -FOR -$"first branch":= OR($"third branch", $"fourth branch") -$"second branch":= OR($1, $2) -$"third branch":= OR($3, $4) -$"fourth branch":= OR($5, $6) -$1:= OFTYPE ns=1;i=5000 -$2:= OFTYPE ns=1;i=5001 -$3:= OFTYPE ns=1;i=5002 -$4:= OFTYPE ns=1;i=5003 -$5:= OFTYPE $7 -$6:= OFTYPE $8 -$7:= ns=1;i=5004 -$8:= i=3035 - - -5. -SELECT - -PATH "/Message", PATH "/Severity", PATH "/EventType" - -WHERE -OR(OR(OR(OFTYPE ns=1;i=5002, $4), OR($5, OFTYPE i=3035)), OR($1,$2)) - -FOR -$1:= OFTYPE $7 -$2:= OFTYPE $8 -$4:= OFTYPE ns=1;i=5003 -$5:= OFTYPE ns=1;i=5004 -$7:= NODEID ns=1;i=5000 -$8:= TYPEID ns=1;i=5001 PATH "." ATTRIBUTE 1 - -*/ \ No newline at end of file diff --git a/examples/events/example_queries/case_3.txt b/examples/events/example_queries/case_3.txt deleted file mode 100644 index 6646033c6..000000000 --- a/examples/events/example_queries/case_3.txt +++ /dev/null @@ -1,17 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - See http://creativecommons.org/publicdomain/zero/1.0/ for more information. -*/ - -SELECT - -PATH "/Message", -PATH "/Severity", -PATH "/EventType" - -WHERE -AND((OFTYPE ns=1;i=5001), $1) - -FOR -$1:= AND($20, $30) -$20:= 99 == Int64 99 -$30:= TYPEID i=5000 PATH "/Severity" > 99 \ No newline at end of file diff --git a/examples/events/example_queries/case_4.txt b/examples/events/example_queries/case_4.txt deleted file mode 100644 index a13874410..000000000 --- a/examples/events/example_queries/case_4.txt +++ /dev/null @@ -1,17 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - See http://creativecommons.org/publicdomain/zero/1.0/ for more information. -*/ - -SELECT - -PATH "/Message", -PATH "/0:Severity", -PATH "/EventType" - -WHERE - -AND($4, TYPEID i=5000 PATH "/Severity" GREATERTHAN $"ref") - -FOR -$"ref":= 99 -$4:= OFTYPE ns=1;i=5000 \ No newline at end of file From 546a527676a59c268d91d08d0f8b01a4a76c30ca Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 30 Oct 2024 18:45:21 +0100 Subject: [PATCH 026/158] refactor(tools): Use AttributeOperand parsing for the ua-cli tool --- tools/ua-cli/ua.c | 236 +++++++++++++++++++++------------------------- 1 file changed, 109 insertions(+), 127 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 7c68ea3c2..e6d398e48 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -7,27 +7,23 @@ #include #include +#include #include #include -#include static UA_Client *client = NULL; static UA_ClientConfig cc; -static UA_NodeId nodeidval = {0}; static char *url = NULL; static char *service = NULL; -static char *nodeid = NULL; -static char *value = NULL; static char *username = NULL; static char *password = NULL; -static UA_UInt32 attr = UA_ATTRIBUTEID_VALUE; #ifdef UA_ENABLE_JSON_ENCODING static UA_Boolean json = false; #endif /* Custom logger that prints to stderr. So the "good output" can be easily separated. */ -UA_LogLevel logLevel = UA_LOGLEVEL_INFO; +UA_LogLevel logLevel = UA_LOGLEVEL_ERROR; /* ANSI escape sequences for color output taken from here: * https://stackoverflow.com/questions/3219393/stdlib-and-colored-output-in-c*/ @@ -76,20 +72,20 @@ UA_Logger stderrLog = {cliLog, NULL, NULL}; static void usage(void) { - fprintf(stderr, "Usage: ua [--json] [--help]\n" + fprintf(stderr, "Usage: ua [options] [--help] \n" " : opc.tcp://domain[:port]\n" - " -> getendpoints: Log the endpoint descriptions of the server\n" - " -> read : Read an attribute of the node\n" - " --attr : Attribute to read from the node. " - "[default: value]\n" + " -> getendpoints: Print the endpoint descriptions of the server\n" + " -> read : Read an attribute\n" //" -> browse : Browse the Node\n" //" -> call : Call the method \n" //" -> write : Write an attribute of the node\n" + " Options:\n" #ifdef UA_ENABLE_JSON_ENCODING " --json: Format output as JSON\n" #endif " --username: Username for the session creation\n" " --password: Password for the session creation\n" + " --loglevel: Logging detail (1 -> TRACE, ..., 6 -> FATAL)\n" " --help: Print this message\n"); exit(EXIT_FAILURE); } @@ -114,37 +110,11 @@ printType(void *p, const UA_DataType *type) { static void abortWithStatus(UA_StatusCode res) { fprintf(stderr, "Aborting with status code %s\n", UA_StatusCode_name(res)); - exit(res); -} - -static void -getEndpoints(int argc, char **argv) { - /* Get endpoints */ - size_t endpointDescriptionsSize = 0; - UA_EndpointDescription* endpointDescriptions = NULL; - UA_StatusCode res = UA_Client_getEndpoints(client, url, - &endpointDescriptionsSize, - &endpointDescriptions); - if(res != UA_STATUSCODE_GOOD) - abortWithStatus(res); - - /* Print the results */ - UA_Variant var; - UA_Variant_setArray(&var, endpointDescriptions, endpointDescriptionsSize, - &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); - printType(&var, &UA_TYPES[UA_TYPES_VARIANT]); - - /* Delete the allocated array */ - UA_Variant_clear(&var); -} - -static void -parseNodeId(void) { - UA_StatusCode res = UA_NodeId_parse(&nodeidval, UA_STRING(nodeid)); - if(res != UA_STATUSCODE_GOOD) { - fprintf(stderr, "Could not parse the NodeId\n"); - exit(EXIT_FAILURE); + if(client) { + UA_Client_disconnect(client); + UA_Client_delete(client); } + exit(res); } static void @@ -164,46 +134,98 @@ connectClient(void) { } static void -readAttr(int argc, char **argv) { - parseNodeId(); - connectClient(); +getEndpoints(int argc, char **argv, int argpos) { + /* Validate the arguments */ + if(argpos != argc) { + fprintf(stderr, "Arguments after \"getendpoints\" could not be parsed\n"); + exit(EXIT_FAILURE); + } - /* Read */ - UA_ReadValueId rvid; - UA_ReadValueId_init(&rvid); - rvid.nodeId = nodeidval; - rvid.attributeId = attr; + /* Get Endpoints */ + size_t endpointDescriptionsSize = 0; + UA_EndpointDescription* endpointDescriptions = NULL; + UA_StatusCode res = UA_Client_getEndpoints(client, url, + &endpointDescriptionsSize, + &endpointDescriptions); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); - UA_ReadRequest req; - UA_ReadRequest_init(&req); - req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; - req.nodesToReadSize = 1; - req.nodesToRead = &rvid; + /* Print the results */ + UA_Variant var; + UA_Variant_setArray(&var, endpointDescriptions, endpointDescriptionsSize, + &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + printType(&var, &UA_TYPES[UA_TYPES_VARIANT]); - UA_ReadResponse resp = UA_Client_Service_read(client, req); - - /* Print the result */ - if(resp.responseHeader.serviceResult == UA_STATUSCODE_GOOD && - resp.resultsSize != 1) - resp.responseHeader.serviceResult = UA_STATUSCODE_BADUNEXPECTEDERROR; - if(resp.responseHeader.serviceResult == UA_STATUSCODE_GOOD) - printType(&resp.results[0], &UA_TYPES[UA_TYPES_DATAVALUE]); - else - abortWithStatus(resp.responseHeader.serviceResult); - - UA_ReadResponse_clear(&resp); - UA_NodeId_clear(&nodeidval); + /* Delete the allocated array */ + UA_Variant_clear(&var); } -static const char *attributeIds[27] = { - "nodeid", "nodeclass", "browsename", "displayname", "description", - "writemask", "userwritemask", "isabstract", "symmetric", "inversename", - "containsnoloops", "eventnotifier", "value", "datatype", "valuerank", - "arraydimensions", "accesslevel", "useraccesslevel", - "minimumsamplinginterval", "historizing", "executable", "userexecutable", - "datatypedefinition", "rolepermissions", "userrolepermissions", - "accessrestrictions", "accesslevelex" -}; +static void +readAttr(int argc, char **argv, int argpos) { + /* Validate the arguments */ + if(argpos != argc - 1) { + fprintf(stderr, "The read service takes the AttributeOperand " + "expression as the last argument\n"); + exit(EXIT_FAILURE); + } + + /* Connect */ + connectClient(); + + /* Parse the AttributeOperand */ + UA_AttributeOperand ao; + UA_StatusCode res = UA_AttributeOperand_parse(&ao, UA_STRING(argv[argpos])); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); + + /* Resolve the RelativePath */ + if(ao.browsePath.elementsSize > 0) { + UA_BrowsePath bp; + UA_BrowsePath_init(&bp); + bp.startingNode = ao.nodeId; + bp.relativePath = ao.browsePath; + + UA_BrowsePathResult bpr = + UA_Client_translateBrowsePathToNodeIds(client, &bp); + if(bpr.statusCode != UA_STATUSCODE_GOOD) + abortWithStatus(bpr.statusCode); + + /* Validate the response */ + if(bpr.targetsSize != 1) { + fprintf(stderr, "The RelativePath did resolve to %u different NodeIds\n", + (unsigned)bpr.targetsSize); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(bpr.targets[0].remainingPathIndex != UA_UINT32_MAX) { + fprintf(stderr, "The RelativePath was not fully resolved\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(!UA_ExpandedNodeId_isLocal(&bpr.targets[0].targetId)) { + fprintf(stderr, "The RelativePath resolves to an ExpandedNodeId " + "on a different server\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + UA_NodeId_clear(&ao.nodeId); + ao.nodeId = bpr.targets[0].targetId.nodeId; + UA_ExpandedNodeId_init(&bpr.targets[0].targetId); + UA_BrowsePathResult_clear(&bpr); + } + + /* Read the attribute */ + UA_ReadValueId rvi; + UA_ReadValueId_init(&rvi); + rvi.nodeId = ao.nodeId; + rvi.attributeId = ao.attributeId; + rvi.indexRange = ao.indexRange; + + UA_DataValue resp = UA_Client_read(client, &rvi); + printType(&resp, &UA_TYPES[UA_TYPES_DATAVALUE]); + UA_DataValue_clear(&resp); + UA_AttributeOperand_clear(&ao); +} /* Parse options beginning with --. * Returns the position in the argv list. */ @@ -211,7 +233,7 @@ static int parseOptions(int argc, char **argv, int argpos) { for(; argpos < argc; argpos++) { /* End of the arguments list */ - if(strncmp(argv[argpos], "--", 2) == 0) + if(strncmp(argv[argpos], "--", 2) != 0) break; /* Help */ @@ -234,29 +256,12 @@ parseOptions(int argc, char **argv, int argpos) { continue; } - /* Parse attribute to be read or written */ - if(strcmp(argv[argpos], "--attr") == 0) { + if(strcmp(argv[argpos], "--loglevel") == 0) { argpos++; if(argpos == argc) usage(); - - /* Try to parse integer attr argument */ - attr = (UA_UInt32)atoi(argv[argpos]); - if(attr != 0) - continue; - - /* Convert to lower case and try to find in table */ - for(char *w = argv[argpos]; *w; w++) - *w = (char)tolower(*w); - for(UA_UInt32 i = 0; i < 26; i++) { - if(strcmp(argv[argpos], attributeIds[i]) == 0) { - attr = i+1; - break; - } - } - if(attr != 0) - continue; - usage(); + logLevel = (UA_LogLevel)atoi(argv[argpos]); + continue; } /* Output JSON format */ @@ -281,38 +286,17 @@ main(int argc, char **argv) { if(argc < 3) usage(); - /* Get the url and service. Must not be a -- option string */ - if(strstr(argv[1], "--") == argv[1] || - strstr(argv[2], "--") == argv[2]) + /* Parse the options */ + int argpos = parseOptions(argc, argv, 1); + if(argpos > argc - 2) usage(); - - url = argv[1]; - service = argv[2]; - - /* If not a -- option string, then this is the NodeId */ - int argpos = 3; - if(argc > argpos && strstr(argv[argpos], "--") != argv[argpos]) { - nodeid = argv[argpos]; - argv[argpos] = NULL; - argpos++; - - /* If not a -- option string, then this is the value */ - if(argc > argpos && strstr(argv[argpos], "--") != argv[argpos]) { - value = argv[argpos]; - argv[argpos] = NULL; - argpos++; - } - } + url = argv[argpos++]; + service = argv[argpos++]; /* Initialize the client config */ cc.logging = &stderrLog; UA_ClientConfig_setDefault(&cc); - /* Parse the options */ - argpos = parseOptions(argc, argv, argpos); - if(argpos < argc - 1) - usage(); /* Not all options have been parsed */ - /* Initialize the client */ client = UA_Client_newWithConfig(&cc); if(!client) { @@ -322,11 +306,9 @@ main(int argc, char **argv) { /* Execute the service */ if(strcmp(service, "getendpoints") == 0) { - if(!nodeid && !value) - getEndpoints(argc, argv); + getEndpoints(argc, argv, argpos); } else if(strcmp(service, "read") == 0) { - if(nodeid && !value) - readAttr(argc, argv); + readAttr(argc, argv, argpos); } else { usage(); /* Unknown service */ } From 4b20fd09a8f2296f38e4b673fbb1c07b238652df Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 30 Oct 2024 18:46:42 +0100 Subject: [PATCH 027/158] refactor(tools): Always assume JSON for the ua-cli tool --- tools/ua-cli/ua.c | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index e6d398e48..732428556 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -18,9 +18,7 @@ static char *url = NULL; static char *service = NULL; static char *username = NULL; static char *password = NULL; -#ifdef UA_ENABLE_JSON_ENCODING -static UA_Boolean json = false; -#endif +int return_value = 0; /* Custom logger that prints to stderr. So the "good output" can be easily separated. */ UA_LogLevel logLevel = UA_LOGLEVEL_ERROR; @@ -80,12 +78,9 @@ usage(void) { //" -> call : Call the method \n" //" -> write : Write an attribute of the node\n" " Options:\n" -#ifdef UA_ENABLE_JSON_ENCODING - " --json: Format output as JSON\n" -#endif " --username: Username for the session creation\n" " --password: Password for the session creation\n" - " --loglevel: Logging detail (1 -> TRACE, ..., 6 -> FATAL)\n" + " --loglevel: Logging detail [1 -> TRACE, 6 -> FATAL]\n" " --help: Print this message\n"); exit(EXIT_FAILURE); } @@ -93,16 +88,13 @@ usage(void) { static void printType(void *p, const UA_DataType *type) { UA_ByteString out = UA_BYTESTRING_NULL; -#ifdef UA_ENABLE_JSON_ENCODING - if(!json) { - UA_print(p, type, &out); - } else { - UA_StatusCode res = UA_encodeJson(p, type, &out, NULL); - (void)res; - } -#else - UA_print(p, type, &out); -#endif + UA_EncodeJsonOptions opts; + memset(&opts, 0, sizeof(UA_EncodeJsonOptions)); + opts.prettyPrint = true; + opts.useReversible = true; + opts.stringNodeIds = true; + UA_StatusCode res = UA_encodeJson(p, type, &out, &opts); + (void)res; printf("%.*s\n", (int)out.length, out.data); UA_ByteString_clear(&out); } @@ -161,10 +153,10 @@ getEndpoints(int argc, char **argv, int argpos) { } static void -readAttr(int argc, char **argv, int argpos) { +read(int argc, char **argv, int argpos) { /* Validate the arguments */ if(argpos != argc - 1) { - fprintf(stderr, "The read service takes the AttributeOperand " + fprintf(stderr, "The read service takes an AttributeOperand " "expression as the last argument\n"); exit(EXIT_FAILURE); } @@ -264,14 +256,6 @@ parseOptions(int argc, char **argv, int argpos) { continue; } - /* Output JSON format */ -#ifdef UA_ENABLE_JSON_ENCODING - if(strcmp(argv[argpos], "--json") == 0) { - json = true; - continue; - } -#endif - /* Unknown option */ usage(); } @@ -322,5 +306,5 @@ main(int argc, char **argv) { //} UA_Client_delete(client); - return 0; + return return_value; } From ebc5f965d1462f4660da6eb1aa578aa1f1abfeef Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 30 Oct 2024 19:57:11 +0100 Subject: [PATCH 028/158] feat(tools): Implement browsing for the ua-cli tool --- tools/ua-cli/ua.c | 80 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 732428556..afe97459f 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -74,7 +74,7 @@ usage(void) { " : opc.tcp://domain[:port]\n" " -> getendpoints: Print the endpoint descriptions of the server\n" " -> read : Read an attribute\n" - //" -> browse : Browse the Node\n" + " -> browse : Browse the references to and from the node\n" //" -> call : Call the method \n" //" -> write : Write an attribute of the node\n" " Options:\n" @@ -219,6 +219,76 @@ read(int argc, char **argv, int argpos) { UA_AttributeOperand_clear(&ao); } +static void +browse(int argc, char **argv, int argpos) { + /* Validate the arguments */ + if(argpos != argc - 1) { + fprintf(stderr, "The browse service takes an AttributeOperand " + "expression as the last argument\n"); + exit(EXIT_FAILURE); + } + + /* Connect */ + connectClient(); + + /* Parse the AttributeOperand */ + UA_AttributeOperand ao; + UA_StatusCode res = UA_AttributeOperand_parse(&ao, UA_STRING(argv[argpos])); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); + + /* Resolve the RelativePath */ + if(ao.browsePath.elementsSize > 0) { + UA_BrowsePath bp; + UA_BrowsePath_init(&bp); + bp.startingNode = ao.nodeId; + bp.relativePath = ao.browsePath; + + UA_BrowsePathResult bpr = + UA_Client_translateBrowsePathToNodeIds(client, &bp); + if(bpr.statusCode != UA_STATUSCODE_GOOD) + abortWithStatus(bpr.statusCode); + + /* Validate the response */ + if(bpr.targetsSize != 1) { + fprintf(stderr, "The RelativePath did resolve to %u different NodeIds\n", + (unsigned)bpr.targetsSize); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(bpr.targets[0].remainingPathIndex != UA_UINT32_MAX) { + fprintf(stderr, "The RelativePath was not fully resolved\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(!UA_ExpandedNodeId_isLocal(&bpr.targets[0].targetId)) { + fprintf(stderr, "The RelativePath resolves to an ExpandedNodeId " + "on a different server\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + UA_NodeId_clear(&ao.nodeId); + ao.nodeId = bpr.targets[0].targetId.nodeId; + UA_ExpandedNodeId_init(&bpr.targets[0].targetId); + UA_BrowsePathResult_clear(&bpr); + } + + /* Read the attribute */ + UA_BrowseDescription bd; + UA_BrowseDescription_init(&bd); + bd.browseDirection = UA_BROWSEDIRECTION_BOTH; + bd.includeSubtypes = true; + bd.nodeId = ao.nodeId; + bd.referenceTypeId = UA_NS0ID(REFERENCES); + bd.resultMask = UA_BROWSERESULTMASK_ALL; + + UA_BrowseResult br = UA_Client_browse(client, NULL, 0, &bd); + + printType(&br, &UA_TYPES[UA_TYPES_BROWSERESULT]); + UA_BrowseResult_clear(&br); + UA_AttributeOperand_clear(&ao); +} + /* Parse options beginning with --. * Returns the position in the argv list. */ static int @@ -292,14 +362,12 @@ main(int argc, char **argv) { if(strcmp(service, "getendpoints") == 0) { getEndpoints(argc, argv, argpos); } else if(strcmp(service, "read") == 0) { - readAttr(argc, argv, argpos); + read(argc, argv, argpos); + } else if(strcmp(service, "browse") == 0) { + browse(argc, argv, argpos); } else { usage(); /* Unknown service */ } - //else if(strcmp(service, "browse") == 0) { - // if(nodeid && !value) - // return browse(argc, argv); - //} //else if(strcmp(argv[1], "write") == 0) { // if(nodeid && value) // return writeAttr(argc-argpos, &argv[argpos]); From c268b12ddba672ca46c11db437c262bb4c03988a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 12 Nov 2024 19:18:54 +0100 Subject: [PATCH 029/158] feat(tools): Implement the write service for the ua-cli tool --- tools/ua-cli/ua.c | 171 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 7 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index afe97459f..744486a73 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -11,6 +11,7 @@ #include #include +#include static UA_Client *client = NULL; static UA_ClientConfig cc; @@ -73,10 +74,10 @@ usage(void) { fprintf(stderr, "Usage: ua [options] [--help] \n" " : opc.tcp://domain[:port]\n" " -> getendpoints: Print the endpoint descriptions of the server\n" - " -> read : Read an attribute\n" - " -> browse : Browse the references to and from the node\n" + " -> read : Read an attribute\n" + " -> browse : Browse the references of a node\n" + " -> write : Write an attribute\n" //" -> call : Call the method \n" - //" -> write : Write an attribute of the node\n" " Options:\n" " --username: Username for the session creation\n" " --password: Password for the session creation\n" @@ -219,6 +220,164 @@ read(int argc, char **argv, int argpos) { UA_AttributeOperand_clear(&ao); } +static void +write(int argc, char **argv, int argpos) { + /* Validate the arguments */ + if(argpos + 1 >= argc) { + fprintf(stderr, "The Write Service takes an AttributeOperand " + "expression and the value as arguments\n"); + exit(EXIT_FAILURE); + } + + /* Parse the AttributeOperand */ + UA_AttributeOperand ao; + UA_StatusCode res = UA_AttributeOperand_parse(&ao, UA_STRING(argv[argpos++])); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); + + /* Aggregate all the remaining arguments and parse them as JSON */ + UA_String valstr = UA_STRING_NULL; + for(; argpos < argc; argpos++) { + UA_String_append(&valstr, UA_STRING(argv[argpos])); + if(argpos != argc - 1) + UA_String_append(&valstr, UA_STRING(" ")); + } + + if(valstr.length == 0) { + fprintf(stderr, "No value defined\n"); + exit(EXIT_FAILURE); + } + + /* Detect a few basic "naked" datatypes. + * Otherwise try to decode a variant */ + UA_Boolean b; + UA_Int32 i; + UA_Float ff; + UA_Variant v; + UA_String s = UA_STRING_NULL; + UA_String f = UA_STRING("false"); + UA_String t = UA_STRING("true"); + if(UA_String_equal(&valstr, &f)) { + b = false; + UA_Variant_setScalar(&v, &b, &UA_TYPES[UA_TYPES_BOOLEAN]); + v.storageType = UA_VARIANT_DATA_NODELETE; + } else if(UA_String_equal(&valstr, &t)) { + b = true; + UA_Variant_setScalar(&v, &b, &UA_TYPES[UA_TYPES_BOOLEAN]); + v.storageType = UA_VARIANT_DATA_NODELETE; + } else if(valstr.data[0] == '\"') { + res = UA_decodeJson(&valstr, &s, &UA_TYPES[UA_TYPES_STRING], NULL); + UA_Variant_setScalar(&v, &s, &UA_TYPES[UA_TYPES_STRING]); + v.storageType = UA_VARIANT_DATA_NODELETE; + } else if(valstr.data[0] >= '0' && valstr.data[0] <= '9') { + res = UA_decodeJson(&valstr, &i, &UA_TYPES[UA_TYPES_INT32], NULL); + UA_Variant_setScalar(&v, &i, &UA_TYPES[UA_TYPES_INT32]); + if(res != UA_STATUSCODE_GOOD) { + res = UA_decodeJson(&valstr, &ff, &UA_TYPES[UA_TYPES_FLOAT], NULL); + UA_Variant_setScalar(&v, &ff, &UA_TYPES[UA_TYPES_FLOAT]); + } + v.storageType = UA_VARIANT_DATA_NODELETE; + } else if(valstr.data[0] == '{') { + /* JSON Variant */ + res = UA_decodeJson(&valstr, &v, &UA_TYPES[UA_TYPES_VARIANT], NULL); + } else if(valstr.data[0] == '(') { + /* Data type name in parentheses */ + UA_STACKARRAY(char, type, valstr.length); + int elem = sscanf((char*)valstr.data, "(%[^)])", type); + if(elem <= 0) { + fprintf(stderr, "Wrong datatype definition\n"); + exit(EXIT_FAILURE); + } + + /* Find type under the name */ + const UA_DataType *datatype = NULL; + for(size_t i = 0; i < UA_TYPES_COUNT; i++) { + if(strcmp(UA_TYPES[i].typeName, type) == 0) { + datatype = &UA_TYPES[i]; + break; + } + } + + if(!datatype) { + fprintf(stderr, "Data type %s unknown\n", type); + exit(EXIT_FAILURE); + } + + valstr.data += 2 + strlen(type); + valstr.length -= 2 + strlen(type); + + /* Parse */ + void *val = UA_new(datatype); + res = UA_decodeJson(&valstr, val, datatype, NULL); + UA_Variant_setScalar(&v, val, datatype); + } else { + res = UA_STATUSCODE_BADDECODINGERROR; + } + + if(res != UA_STATUSCODE_GOOD) { + fprintf(stderr, "Could not parse the value\n"); + exit(EXIT_FAILURE); + } + + /* Connect */ + connectClient(); + + /* Resolve the RelativePath */ + if(ao.browsePath.elementsSize > 0) { + UA_BrowsePath bp; + UA_BrowsePath_init(&bp); + bp.startingNode = ao.nodeId; + bp.relativePath = ao.browsePath; + + UA_BrowsePathResult bpr = + UA_Client_translateBrowsePathToNodeIds(client, &bp); + if(bpr.statusCode != UA_STATUSCODE_GOOD) + abortWithStatus(bpr.statusCode); + + /* Validate the response */ + if(bpr.targetsSize != 1) { + fprintf(stderr, "The RelativePath did resolve to %u different NodeIds\n", + (unsigned)bpr.targetsSize); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(bpr.targets[0].remainingPathIndex != UA_UINT32_MAX) { + fprintf(stderr, "The RelativePath was not fully resolved\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(!UA_ExpandedNodeId_isLocal(&bpr.targets[0].targetId)) { + fprintf(stderr, "The RelativePath resolves to an ExpandedNodeId " + "on a different server\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + UA_NodeId_clear(&ao.nodeId); + ao.nodeId = bpr.targets[0].targetId.nodeId; + UA_ExpandedNodeId_init(&bpr.targets[0].targetId); + UA_BrowsePathResult_clear(&bpr); + } + + /* Write the attribute */ + UA_WriteValue wv; + UA_WriteValue_init(&wv); + wv.value.value = v; + wv.value.hasValue = true; + wv.nodeId = ao.nodeId; + wv.attributeId = ao.attributeId; + wv.indexRange = ao.indexRange; + res = UA_Client_write(client, &wv); + + /* Print the StatusCode and return */ + fprintf(stdout, "%s\n", UA_StatusCode_name(res)); + if(res != UA_STATUSCODE_GOOD) + return_value = EXIT_FAILURE; + + UA_AttributeOperand_clear(&ao); + UA_Variant_clear(&v); + UA_String_clear(&s); +} + static void browse(int argc, char **argv, int argpos) { /* Validate the arguments */ @@ -365,13 +524,11 @@ main(int argc, char **argv) { read(argc, argv, argpos); } else if(strcmp(service, "browse") == 0) { browse(argc, argv, argpos); + } else if(strcmp(service, "write") == 0) { + write(argc, argv, argpos); } else { usage(); /* Unknown service */ } - //else if(strcmp(argv[1], "write") == 0) { - // if(nodeid && value) - // return writeAttr(argc-argpos, &argv[argpos]); - //} UA_Client_delete(client); return return_value; From e35791cbb743c7ef3cabb5bb5d86ceb5ad6ec137 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 00:16:05 +0100 Subject: [PATCH 030/158] feat(tools): The ua-cli tool supports encryption via certificate files --- tools/ua-cli/ua.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 744486a73..ad6569f9b 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,8 @@ static char *url = NULL; static char *service = NULL; static char *username = NULL; static char *password = NULL; +static UA_ByteString certificate; +static UA_ByteString privateKey; int return_value = 0; /* Custom logger that prints to stderr. So the "good output" can be easily separated. */ @@ -81,11 +84,36 @@ usage(void) { " Options:\n" " --username: Username for the session creation\n" " --password: Password for the session creation\n" - " --loglevel: Logging detail [1 -> TRACE, 6 -> FATAL]\n" + " --certificate : Certificate in DER format\n" + " --privatekey : Private key in DER format\n" + " --loglevel : Logging detail [1 -> TRACE, 6 -> FATAL]\n" " --help: Print this message\n"); exit(EXIT_FAILURE); } +static UA_ByteString +loadFile(const char *const path) { + /* Open the file */ + FILE *fp = fopen(path, "rb"); + if(!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + exit(EXIT_FAILURE); + } + + /* Get the file length, allocate the data and read */ + UA_ByteString fileContents = UA_STRING_NULL; + fseek(fp, 0, SEEK_END); + fileContents.length = (size_t)ftell(fp); + fileContents.data = (UA_Byte *)UA_malloc(fileContents.length * sizeof(UA_Byte)); + fseek(fp, 0, SEEK_SET); + size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp); + if(read == 0) + UA_ByteString_clear(&fileContents); + fclose(fp); + + return fileContents; +} + static void printType(void *p, const UA_DataType *type) { UA_ByteString out = UA_BYTESTRING_NULL; @@ -485,6 +513,22 @@ parseOptions(int argc, char **argv, int argpos) { continue; } + if(strcmp(argv[argpos], "--certificate") == 0) { + argpos++; + if(argpos == argc) + usage(); + certificate = loadFile(argv[argpos]); + continue; + } + + if(strcmp(argv[argpos], "--privatekey") == 0) { + argpos++; + if(argpos == argc) + usage(); + privateKey = loadFile(argv[argpos]); + continue; + } + /* Unknown option */ usage(); } @@ -510,6 +554,18 @@ main(int argc, char **argv) { cc.logging = &stderrLog; UA_ClientConfig_setDefault(&cc); + /* TODO: Trustlist end revocation list */ + if(certificate.length > 0) { + UA_StatusCode res = + UA_ClientConfig_setDefaultEncryption(&cc, certificate, privateKey, + NULL, 0, NULL, 0); + if(res != UA_STATUSCODE_GOOD) + exit(EXIT_FAILURE); + } + + cc.certificateVerification.clear(&cc.certificateVerification); + UA_CertificateGroup_AcceptAll(&cc.certificateVerification); + /* Initialize the client */ client = UA_Client_newWithConfig(&cc); if(!client) { @@ -530,6 +586,9 @@ main(int argc, char **argv) { usage(); /* Unknown service */ } + UA_ByteString_clear(&certificate); + UA_ByteString_clear(&privateKey); + UA_Client_delete(client); return return_value; } From 7e45f58d70217f7a247b65e8ab7d73101c881ce2 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 13 Nov 2024 08:26:15 +0100 Subject: [PATCH 031/158] feat(tools): The ua-cli tool can select endpoints based on SecurityPolicyUri --- tools/ua-cli/ua.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index ad6569f9b..00eec52a9 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -22,6 +22,7 @@ static char *username = NULL; static char *password = NULL; static UA_ByteString certificate; static UA_ByteString privateKey; +static UA_ByteString securityPolicyUri; int return_value = 0; /* Custom logger that prints to stderr. So the "good output" can be easily separated. */ @@ -86,7 +87,8 @@ usage(void) { " --password: Password for the session creation\n" " --certificate : Certificate in DER format\n" " --privatekey : Private key in DER format\n" - " --loglevel : Logging detail [1 -> TRACE, 6 -> FATAL]\n" + " --securitypolicy : SecurityPolicy to be used\n" + " --loglevel : Logging detail [0 -> TRACE, 6 -> FATAL]\n" " --help: Print this message\n"); exit(EXIT_FAILURE); } @@ -529,6 +531,14 @@ parseOptions(int argc, char **argv, int argpos) { continue; } + if(strcmp(argv[argpos], "--securitypolicy") == 0) { + argpos++; + if(argpos == argc) + usage(); + securityPolicyUri = UA_STRING_ALLOC(argv[argpos]); + continue; + } + /* Unknown option */ usage(); } @@ -563,9 +573,14 @@ main(int argc, char **argv) { exit(EXIT_FAILURE); } + /* Accept all certificates without a trustlist */ cc.certificateVerification.clear(&cc.certificateVerification); UA_CertificateGroup_AcceptAll(&cc.certificateVerification); + /* Filter endpoints with the securitypolicy. + * The allocated string gets cleaned up as part of the client config. */ + cc.securityPolicyUri = securityPolicyUri; + /* Initialize the client */ client = UA_Client_newWithConfig(&cc); if(!client) { From ed2194f105fc89830dc11e32b0dfa8142b2d78f1 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 3 Dec 2024 20:25:57 +0100 Subject: [PATCH 032/158] feat(tools): Implement the "explore" tool in the ua-cli --- tools/ua-cli/ua.c | 162 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 156 insertions(+), 6 deletions(-) diff --git a/tools/ua-cli/ua.c b/tools/ua-cli/ua.c index 00eec52a9..228dce8f0 100644 --- a/tools/ua-cli/ua.c +++ b/tools/ua-cli/ua.c @@ -81,6 +81,7 @@ usage(void) { " -> read : Read an attribute\n" " -> browse : Browse the references of a node\n" " -> write : Write an attribute\n" + " -> explore [--depth ]: Print the structure of the information model below the indicated node\n" //" -> call : Call the method \n" " Options:\n" " --username: Username for the session creation\n" @@ -184,7 +185,7 @@ getEndpoints(int argc, char **argv, int argpos) { } static void -read(int argc, char **argv, int argpos) { +readService(int argc, char **argv, int argpos) { /* Validate the arguments */ if(argpos != argc - 1) { fprintf(stderr, "The read service takes an AttributeOperand " @@ -251,7 +252,7 @@ read(int argc, char **argv, int argpos) { } static void -write(int argc, char **argv, int argpos) { +writeService(int argc, char **argv, int argpos) { /* Validate the arguments */ if(argpos + 1 >= argc) { fprintf(stderr, "The Write Service takes an AttributeOperand " @@ -409,7 +410,7 @@ write(int argc, char **argv, int argpos) { } static void -browse(int argc, char **argv, int argpos) { +browseService(int argc, char **argv, int argpos) { /* Validate the arguments */ if(argpos != argc - 1) { fprintf(stderr, "The browse service takes an AttributeOperand " @@ -478,6 +479,151 @@ browse(int argc, char **argv, int argpos) { UA_AttributeOperand_clear(&ao); } +static char *nodeClassNames[] = { + "Unspecified ", + "Object ", + "Variable ", + "Method ", + "ObjectType ", + "VariableType ", + "ReferenceType", + "DataType ", + "View " +}; + +static void +exploreRecursive(char *pathString, size_t pos, const UA_NodeId current, + UA_NodeClass nc, size_t depth) { + size_t targetlevel = 0; + while(nc) { + ++targetlevel; + nc = (UA_NodeClass)((size_t)nc >> 1); + } + printf("%s %.*s\n", nodeClassNames[targetlevel], (int)pos, pathString); + + if(depth == 0) + return; + + /* Read the attribute */ + UA_BrowseDescription bd; + UA_BrowseDescription_init(&bd); + bd.browseDirection = UA_BROWSEDIRECTION_FORWARD; + bd.includeSubtypes = true; + bd.nodeId = current; + bd.referenceTypeId = UA_NS0ID(HIERARCHICALREFERENCES); + bd.resultMask = UA_BROWSERESULTMASK_BROWSENAME | UA_BROWSERESULTMASK_NODECLASS; + + UA_BrowseResult br = UA_Client_browse(client, NULL, 0, &bd); + + for(size_t i = 0; i < br.referencesSize; i++) { + UA_ReferenceDescription *rd = &br.references[i]; + if(!UA_ExpandedNodeId_isLocal(&rd->nodeId)) + continue; + char browseName[80]; + int len = snprintf(browseName, 80, "%d:%.*s", + (unsigned)rd->browseName.namespaceIndex, + (int)rd->browseName.name.length, + (char*)rd->browseName.name.data); + if(len < 0) + continue; + if(len > 80) + len = 80; + memcpy(pathString + pos, "/", 1); + memcpy(pathString + pos + 1, browseName, len); + exploreRecursive(pathString, pos + 1 + (size_t)len, rd->nodeId.nodeId, rd->nodeClass, depth-1); + } + + UA_BrowseResult_clear(&br); +} + +static void +explore(int argc, char **argv, int argpos) { + /* Parse the arguments */ + char *pathArg = NULL; + size_t depth = 20; + + for(; argpos < argc; argpos++) { + /* AttributeOperand */ + if(strncmp(argv[argpos], "--", 2) != 0) { + if(pathArg != NULL) + usage(); + pathArg = argv[argpos]; + continue; + } + + /* Maximum depth */ + if(strcmp(argv[argpos], "--depth") == 0) { + argpos++; + if(argpos == argc) + usage(); + depth = (size_t)atoi(argv[argpos]); + continue; + } + + /* Unknown */ + usage(); + } + + /* Connect */ + connectClient(); + + /* Parse the AttributeOperand */ + UA_AttributeOperand ao; + UA_StatusCode res = UA_AttributeOperand_parse(&ao, UA_STRING(pathArg)); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); + + /* Resolve the RelativePath */ + if(ao.browsePath.elementsSize > 0) { + UA_BrowsePath bp; + UA_BrowsePath_init(&bp); + bp.relativePath = ao.browsePath; + bp.startingNode = ao.nodeId; + + UA_BrowsePathResult bpr = + UA_Client_translateBrowsePathToNodeIds(client, &bp); + if(bpr.statusCode != UA_STATUSCODE_GOOD) + abortWithStatus(bpr.statusCode); + + /* Validate the response */ + if(bpr.targetsSize != 1) { + fprintf(stderr, "The RelativePath did resolve to %u different NodeIds\n", + (unsigned)bpr.targetsSize); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(bpr.targets[0].remainingPathIndex != UA_UINT32_MAX) { + fprintf(stderr, "The RelativePath was not fully resolved\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + if(!UA_ExpandedNodeId_isLocal(&bpr.targets[0].targetId)) { + fprintf(stderr, "The RelativePath resolves to an ExpandedNodeId " + "on a different server\n"); + abortWithStatus(UA_STATUSCODE_BADINTERNALERROR); + } + + UA_NodeId_clear(&ao.nodeId); + ao.nodeId = bpr.targets[0].targetId.nodeId; + UA_ExpandedNodeId_init(&bpr.targets[0].targetId); + UA_BrowsePathResult_clear(&bpr); + } + + /* Read the NodeClass of the root node */ + UA_NodeClass nc = UA_NODECLASS_UNSPECIFIED; + res = UA_Client_readNodeClassAttribute(client, ao.nodeId, &nc); + if(res != UA_STATUSCODE_GOOD) + abortWithStatus(res); + + char relativepath[512]; + size_t pos = strlen(pathArg); + memcpy(relativepath, pathArg, pos); + + exploreRecursive(relativepath, pos, ao.nodeId, nc, depth); + + UA_AttributeOperand_clear(&ao); +} + /* Parse options beginning with --. * Returns the position in the argv list. */ static int @@ -565,6 +711,7 @@ main(int argc, char **argv) { UA_ClientConfig_setDefault(&cc); /* TODO: Trustlist end revocation list */ +#ifdef UA_ENABLE_ENCRYPTION if(certificate.length > 0) { UA_StatusCode res = UA_ClientConfig_setDefaultEncryption(&cc, certificate, privateKey, @@ -572,6 +719,7 @@ main(int argc, char **argv) { if(res != UA_STATUSCODE_GOOD) exit(EXIT_FAILURE); } +#endif /* Accept all certificates without a trustlist */ cc.certificateVerification.clear(&cc.certificateVerification); @@ -592,11 +740,13 @@ main(int argc, char **argv) { if(strcmp(service, "getendpoints") == 0) { getEndpoints(argc, argv, argpos); } else if(strcmp(service, "read") == 0) { - read(argc, argv, argpos); + readService(argc, argv, argpos); } else if(strcmp(service, "browse") == 0) { - browse(argc, argv, argpos); + browseService(argc, argv, argpos); } else if(strcmp(service, "write") == 0) { - write(argc, argv, argpos); + writeService(argc, argv, argpos); + } else if(strcmp(service, "explore") == 0) { + explore(argc, argv, argpos); } else { usage(); /* Unknown service */ } From b6c113012aab01370b9c32cd74eafa96ce7ebb02 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 9 Dec 2024 21:13:47 +0100 Subject: [PATCH 033/158] refactor(plugins): Parse the certificate subject alt name using the new mbedTLS API in mbedtls/certificategroup.c --- plugins/crypto/mbedtls/certificategroup.c | 49 ++++++++++++++++++++--- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index 10f7f72c1..41ae76f58 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -696,6 +696,8 @@ cleanup: return retval; } +#if !defined(mbedtls_x509_subject_alternative_name) + /* Find binary substring. Taken and adjusted from * http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */ @@ -735,6 +737,8 @@ UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l return NULL; } +#endif + UA_StatusCode UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, const UA_ByteString *certificate, @@ -748,21 +752,56 @@ UA_CertificateUtils_verifyApplicationURI(UA_RuleHandling ruleHandling, if(retval != UA_STATUSCODE_GOOD) return retval; +#if defined(mbedtls_x509_subject_alternative_name) + /* Get the Subject Alternative Name and compate */ + mbedtls_x509_subject_alternative_name san; + mbedtls_x509_sequence *cur = &remoteCertificate.subject_alt_names; + retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID; + for(; cur; cur = cur->next) { + int res = mbedtls_x509_parse_subject_alt_name(&cur->buf, &san); + if(res != 0) + continue; + if(san.type != MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER) { + mbedtls_x509_free_subject_alt_name(&san); + continue; + } + + UA_String uri = {san.san.unstructured_name.len, san.san.unstructured_name.p}; + UA_Boolean found = UA_String_equal(&uri, applicationURI); + if(found) { + retval = UA_STATUSCODE_GOOD; + } else if(ruleHandling != UA_RULEHANDLING_ACCEPT) { + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, + "The certificate's Subject Alternative Name URI (%S) " + "does not match the ApplicationURI (%S)", + uri, *applicationURI); + } + mbedtls_x509_free_subject_alt_name(&san); + break; + } + + if(!cur && ruleHandling != UA_RULEHANDLING_ACCEPT) { + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, + "The certificate has no Subject Alternative Name URI defined"); + } +#else /* Poor man's ApplicationUri verification. mbedTLS does not parse all fields * of the Alternative Subject Name. Instead test whether the URI-string is - * present in the v3_ext field in general. - * - * TODO: Improve parsing of the Alternative Subject Name */ + * present in the v3_ext field in general. */ if(UA_Bstrstr(remoteCertificate.v3_ext.p, remoteCertificate.v3_ext.len, applicationURI->data, applicationURI->length) == NULL) retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID; - if(retval != UA_STATUSCODE_GOOD && ruleHandling == UA_RULEHANDLING_DEFAULT) { + if(retval != UA_STATUSCODE_GOOD && ruleHandling != UA_RULEHANDLING_ACCEPT) { UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURITYPOLICY, "The certificate's application URI could not be verified. StatusCode %s", UA_StatusCode_name(retval)); - retval = UA_STATUSCODE_GOOD; } +#endif + + if(ruleHandling != UA_RULEHANDLING_ABORT) + retval = UA_STATUSCODE_GOOD; + mbedtls_x509_crt_free(&remoteCertificate); return retval; } From 25584c3ccf273cf45c9f8918d26a24754b431236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Tyndel?= Date: Wed, 11 Dec 2024 09:25:28 +0100 Subject: [PATCH 034/158] fix(plugin): Monitor 'trusted' subdirectory in PKI inotify doesn't work recursively, subdirectories need to be monitored separately. As a result adding certificate to 'trusted' directory directly didn't refresh the trustlist. --- plugins/crypto/ua_certificategroup_filestore.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/crypto/ua_certificategroup_filestore.c b/plugins/crypto/ua_certificategroup_filestore.c index fd6a08450..f4aab07fd 100644 --- a/plugins/crypto/ua_certificategroup_filestore.c +++ b/plugins/crypto/ua_certificategroup_filestore.c @@ -461,10 +461,19 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { if(context->inotifyFd == -1) return UA_STATUSCODE_BADINTERNALERROR; - char rootFolder[PATH_MAX] = {0}; - mp_snprintf(rootFolder, PATH_MAX, "%.*s", + char folder[PATH_MAX] = {0}; + mp_snprintf(folder, PATH_MAX, "%.*s", (int)context->rootFolder.length, (char*)context->rootFolder.data); - int wd = inotify_add_watch(context->inotifyFd, rootFolder, IN_ALL_EVENTS); + int wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); + if(wd == -1) { + close(context->inotifyFd); + context->inotifyFd = -1; + return UA_STATUSCODE_BADINTERNALERROR; + } + + mp_snprintf(folder, PATH_MAX, "%.*s", + (int)context->trustedCertFolder.length, (char*)context->trustedCertFolder.data); + wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); if(wd == -1) { close(context->inotifyFd); context->inotifyFd = -1; From ed46888de1a3a7e949bd8ea2591474a8d1c80c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Tyndel?= Date: Wed, 11 Dec 2024 09:05:38 +0100 Subject: [PATCH 035/158] fix(client): Fixed log message crashing application Wrong data were passed to logger, which resulted in segmentation fault. --- src/client/ua_client_connect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index 2cf1f40fb..a4005dc25 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -1618,7 +1618,7 @@ verifyClientApplicationURI(const UA_Client *client) { UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, "The configured ApplicationURI does not match the URI " "specified in the certificate for the SecurityPolicy %S", - sp->policyUri.length); + sp->policyUri); } } #endif From 17ca7f23c06c69b9112db91cad256878942439a5 Mon Sep 17 00:00:00 2001 From: Rolf Kalbermatter <15158041+RolfKal@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:38:19 +0100 Subject: [PATCH 036/158] fix(types): Make UA_String_append an exported function --- include/open62541/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 2b710bbef..21d3ad9b5 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -200,7 +200,7 @@ UA_String_fromChars(const char *src) UA_FUNC_ATTR_WARN_UNUSED_RESULT; UA_Boolean UA_EXPORT UA_String_isEmpty(const UA_String *s); -UA_StatusCode +UA_StatusCode UA_EXPORT UA_String_append(UA_String *s, const UA_String s2); UA_EXPORT extern const UA_String UA_STRING_NULL; From 3d9c5e13c961de63ce6767d850f3701e08e7af33 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Thu, 12 Dec 2024 10:25:26 +0100 Subject: [PATCH 037/158] fix(server): Correct the callback assignment for removing certificates --- src/server/ua_server_ns0_gds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/ua_server_ns0_gds.c b/src/server/ua_server_ns0_gds.c index 54c30ff99..f61f14e65 100644 --- a/src/server/ua_server_ns0_gds.c +++ b/src/server/ua_server_ns0_gds.c @@ -1715,7 +1715,7 @@ initNS0PushManagement(UA_Server *server) { retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_TRUSTLISTTYPE_ADDCERTIFICATE), addCertificateAction); retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST_REMOVECERTIFICATE), removeCertificateAction); - retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP_TRUSTLIST_ADDCERTIFICATE), removeCertificateAction); + retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP_TRUSTLIST_REMOVECERTIFICATE), removeCertificateAction); retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_TRUSTLISTTYPE_REMOVECERTIFICATE), removeCertificateAction); retval |= setMethodNode_callback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST_OPENWITHMASKS), openTrustListWithMaskAction); From 152763151379d2b654a0f04f28d9f1dd8606adc4 Mon Sep 17 00:00:00 2001 From: Marwin Glaser Date: Tue, 17 Dec 2024 17:54:06 +0100 Subject: [PATCH 038/158] fix(ci): remove python-six dependency from doc_upload workflow --- .github/workflows/doc_upload.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc_upload.yml b/.github/workflows/doc_upload.yml index 749e3c6e4..11fc5d177 100644 --- a/.github/workflows/doc_upload.yml +++ b/.github/workflows/doc_upload.yml @@ -20,7 +20,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install -y -qq python3-sphinx graphviz python-six texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra + sudo apt-get install -y -qq python3-sphinx graphviz texlive-fonts-recommended texlive-latex-extra texlive-plain-generic texlive-latex-recommended latexmk texlive-fonts-extra pip install sphinx-rtd-theme - name: Build Documentation run: source tools/ci/ci.sh && build_docs_pdf From 3ec84bf5c0b4ec7d2e27c4081551b02bcc07539e Mon Sep 17 00:00:00 2001 From: Marwin Glaser Date: Tue, 17 Dec 2024 15:57:41 +0100 Subject: [PATCH 039/158] fix(deps): fix dependabot github-actions config --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1d9d67414..f30e93eea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: schedule: interval: "weekly" - package-ecosystem: "github-actions" - directory: ".github" + directory: "/" target-branch: "master" schedule: interval: "weekly" From 62a9ea453fec8f3707bdff9e85d4173ea084074f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:20:33 +0000 Subject: [PATCH 040/158] build(deps): bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d402eeaca..722b176c8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -29,4 +29,4 @@ jobs: run: | tree . - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 From f59e15f4f4aedad5621c707a7ab7ef0798a40b3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:20:30 +0000 Subject: [PATCH 041/158] build(deps): bump actions/checkout from 2 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 722b176c8..0c851f5d4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,7 +18,7 @@ jobs: sudo apt-get update sudo apt-get install -y -qq python3-sphinx graphviz check libmbedtls-dev mosquitto - name: Fetch - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true - name: Execute Tests From fe41de07e7d87faa9ac1253627bd3e7f2edea1ae Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 17 Dec 2024 01:06:50 -0500 Subject: [PATCH 042/158] fix(core): Remove redundant decrement of mapSize --- src/util/ua_util.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/ua_util.c b/src/util/ua_util.c index d0b00a83a..17a719965 100644 --- a/src/util/ua_util.c +++ b/src/util/ua_util.c @@ -464,7 +464,6 @@ UA_KeyValueMap_remove(UA_KeyValueMap *map, UA_Array_resize((void**)&map->map, &map->mapSize, map->mapSize - 1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); (void)res; - map->mapSize--; return UA_STATUSCODE_GOOD; } From 76a0d171eb15ce1a421c5ba72d89a23c26280cfa Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 17 Dec 2024 01:07:19 -0500 Subject: [PATCH 043/158] test(core): Add test case for removals from KeyValueMap --- tests/check_kvm_utils.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/check_kvm_utils.c b/tests/check_kvm_utils.c index eace60b18..a2b546a68 100644 --- a/tests/check_kvm_utils.c +++ b/tests/check_kvm_utils.c @@ -69,7 +69,6 @@ START_TEST(CheckKVMContains) { END_TEST START_TEST(CheckKVMCopy) { - UA_KeyValueMap *kvm = keyValueMap_setup(10, 0, 0); UA_KeyValueMap kvmCopy; UA_KeyValueMap_copy(kvm, &kvmCopy); @@ -82,6 +81,20 @@ START_TEST(CheckKVMCopy) { } END_TEST +START_TEST(CheckKVMRemove) { + UA_KeyValueMap *kvm = keyValueMap_setup(10, 0, 0); + + for(size_t i = 0; i < 10; i++) { + char key[10]; + snprintf(key, 10, "key%02d", (UA_UInt16) i); + UA_StatusCode retval = UA_KeyValueMap_remove(kvm, UA_QUALIFIEDNAME(0, key)); + ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD); + ck_assert_uint_eq(kvm->mapSize, 9-i); + } + UA_KeyValueMap_delete(kvm); +} +END_TEST + START_TEST(CheckKVMCountMergedIntersecting) { UA_KeyValueMap *kvmLhs = keyValueMap_setup(10, 0, 0); UA_KeyValueMap *kvmRhs = keyValueMap_setup(10, 5, 10); @@ -167,6 +180,7 @@ int main(void) { tcase_add_test(tc, CheckNullArgs); tcase_add_test(tc, CheckKVMContains); tcase_add_test(tc, CheckKVMCopy); + tcase_add_test(tc, CheckKVMRemove); tcase_add_test(tc, CheckKVMCountMergedIntersecting); tcase_add_test(tc, CheckKVMCountMergedComplementary); tcase_add_test(tc, CheckKVMCountMergedCommon); From 730eb6728f4feac891894055418ae00118459c1e Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 17 Dec 2024 01:10:00 -0500 Subject: [PATCH 044/158] fix(server): Add missing initialization for session attributes --- src/server/ua_session.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/server/ua_session.c b/src/server/ua_session.c index 4feba65ca..5be4717ed 100644 --- a/src/server/ua_session.c +++ b/src/server/ua_session.c @@ -287,10 +287,13 @@ UA_Server_setSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, return UA_STATUSCODE_BADNOTWRITABLE; UA_LOCK(&server->serviceMutex); UA_Session *session = getSessionById(server, sessionId); - UA_StatusCode res = UA_STATUSCODE_BADSESSIONIDINVALID; - if(session) - res = UA_KeyValueMap_set(session->attributes, - key, value); + if(!session) { + UA_UNLOCK(&server->serviceMutex); + return UA_STATUSCODE_BADSESSIONIDINVALID; + } + if(!session->attributes) + session->attributes = UA_KeyValueMap_new(); + UA_StatusCode res = UA_KeyValueMap_set(session->attributes, key, value); UA_UNLOCK(&server->serviceMutex); return res; } From 006e837dbb4cd958640387eaf880e2093519dc40 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 17 Dec 2024 01:11:19 -0500 Subject: [PATCH 045/158] fix(server): Correct status code when deleting non-existing session attributes --- src/server/ua_session.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/server/ua_session.c b/src/server/ua_session.c index 5be4717ed..6c227c322 100644 --- a/src/server/ua_session.c +++ b/src/server/ua_session.c @@ -309,8 +309,10 @@ UA_Server_deleteSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, UA_UNLOCK(&server->serviceMutex); return UA_STATUSCODE_BADSESSIONIDINVALID; } - UA_StatusCode res = - UA_KeyValueMap_remove(session->attributes, key); + UA_StatusCode res = UA_STATUSCODE_BADNOTFOUND; + if (session->attributes) { + res = UA_KeyValueMap_remove(session->attributes, key); + } UA_UNLOCK(&server->serviceMutex); return res; } From dd2275cec0aaea77d6d4bf6d95fc87cb52b4c748 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 17 Dec 2024 23:07:17 -0500 Subject: [PATCH 046/158] test(server): Add test case for setting a session attribute --- tests/server/check_session.c | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/server/check_session.c b/tests/server/check_session.c index 76b21be13..0ea60c061 100644 --- a/tests/server/check_session.c +++ b/tests/server/check_session.c @@ -114,6 +114,50 @@ START_TEST(Session_updateLifetime_ShallWork) { } END_TEST +START_TEST(Session_setSessionAttribute_ShallWork) { + UA_Client *client = UA_Client_newForUnitTest(); + UA_StatusCode retval = UA_Client_connectSecureChannel(client, "opc.tcp://localhost:4840"); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + + /* CreateSession */ + UA_CreateSessionRequest createReq; + UA_CreateSessionResponse createRes; + UA_CreateSessionRequest_init(&createReq); + __UA_Client_Service(client, &createReq, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST], + &createRes, &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE]); + + ck_assert_uint_eq(createRes.responseHeader.serviceResult, UA_STATUSCODE_GOOD); + + /* Manually splice the AuthenticationToken into the client. So that it is + * added to the Request. */ + UA_NodeId_copy(&createRes.authenticationToken, &client->authenticationToken); + + /* Set an attribute for the session. */ + UA_QualifiedName key = UA_QUALIFIEDNAME(1, "myAttribute");; + UA_Variant *variant = UA_Variant_new(); + UA_Variant_init(variant); + status s = UA_Server_setSessionAttribute(server, &createRes.sessionId, key, variant); + UA_Variant_delete(variant); + ck_assert_int_eq(s, UA_STATUSCODE_GOOD); + + /* CloseSession */ + UA_CloseSessionRequest closeReq; + UA_CloseSessionResponse closeRes; + UA_CloseSessionRequest_init(&closeReq); + + __UA_Client_Service(client, &closeReq, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], + &closeRes, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE]); + + ck_assert_uint_eq(closeRes.responseHeader.serviceResult, UA_STATUSCODE_GOOD); + + UA_CloseSessionResponse_clear(&closeRes); + UA_CreateSessionResponse_clear(&createRes); + + UA_Client_disconnect(client); + UA_Client_delete(client); +} +END_TEST + static Suite* testSuite_Session(void) { Suite *s = suite_create("Session"); TCase *tc_session = tcase_create("Core"); @@ -121,6 +165,7 @@ static Suite* testSuite_Session(void) { tcase_add_test(tc_session, Session_close_before_activate); tcase_add_test(tc_session, Session_init_ShallWork); tcase_add_test(tc_session, Session_updateLifetime_ShallWork); + tcase_add_test(tc_session, Session_setSessionAttribute_ShallWork); suite_add_tcase(s,tc_session); return s; } From 8c0f6487deb32cb98f4c24724d6eb6abea33cf43 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 19 Dec 2024 09:50:05 +0100 Subject: [PATCH 047/158] refactor(pubsub): Multiple DataSetMessages are possible also with the PayloadHeader disabled --- CHANGES.md | 11 ++ include/open62541/pubsub.h | 15 +- src/pubsub/ua_pubsub_networkmessage_binary.c | 139 +++++++------------ src/pubsub/ua_pubsub_networkmessage_json.c | 61 +++----- src/pubsub/ua_pubsub_reader.c | 19 +-- src/pubsub/ua_pubsub_readergroup.c | 7 +- src/pubsub/ua_pubsub_writergroup.c | 31 ++--- tests/pubsub/check_pubsub_encoding.c | 51 ++++--- tests/pubsub/check_pubsub_encoding_custom.c | 18 +++ tests/pubsub/check_pubsub_encoding_json.c | 47 +++---- 10 files changed, 179 insertions(+), 220 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 478a6867b..64818addb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,17 @@ refactorings and bug fixes are not reported here. # Development +### PubSub NetworkMessage structure has an explicit DataSetMessageSize + +In prior versions of the standard, when the PayloadHeader was missing, the +PubSub NetworkMessage needed to have exactly one DataSetMessage. In the current +standard there can be several DataSetMessages also without a PayloadHeader. To +allow for this the UA_NetworkMessage structure now contains an explicit +DataSetMessageSize field outside of the PayloadHeader. + +Note that code could before rely on the default of one DataSetMessage. This code +needs to be revised to set the new DataSetMessageSize field to one. + ### PubSub Components are disabled initially PubSubComponents (PubSubConnections, ReaderGroups, ...) are no longer enabled diff --git a/include/open62541/pubsub.h b/include/open62541/pubsub.h index db605dd0e..0b400d1e0 100644 --- a/include/open62541/pubsub.h +++ b/include/open62541/pubsub.h @@ -29,11 +29,6 @@ _UA_BEGIN_DECLS * DataSet Message * ^^^^^^^^^^^^^^^ */ -typedef struct { - UA_Byte count; - UA_UInt16* dataSetWriterIds; -} UA_DataSetPayloadHeader; - typedef enum { UA_FIELDENCODING_VARIANT = 0, UA_FIELDENCODING_RAWDATA = 1, @@ -87,6 +82,8 @@ typedef struct { } UA_DataSetMessage_DataDeltaFrameData; typedef struct { + UA_UInt16 dataSetWriterId; /* Goes into the payload header */ + UA_DataSetMessageHeader header; union { UA_DataSetMessage_DataKeyFrameData keyFrameData; @@ -106,8 +103,8 @@ typedef enum { } UA_NetworkMessageType; typedef struct { - UA_UInt16* sizes; - UA_DataSetMessage* dataSetMessages; + UA_DataSetMessage *dataSetMessages; + size_t dataSetMessagesSize; /* Goes into the payload header */ } UA_DataSetPayload; typedef struct { @@ -153,10 +150,6 @@ typedef struct { UA_NetworkMessageGroupHeader groupHeader; - union { - UA_DataSetPayloadHeader dataSetPayloadHeader; - } payloadHeader; - UA_DateTime timestamp; UA_UInt16 picoseconds; UA_UInt16 promotedFieldsSize; diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index cfefc748e..b62ca1b02 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -152,8 +152,7 @@ UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffe rv = DECODE_BINARY(&nm->groupHeader.writerGroupId, UINT16); break; case UA_PUBSUB_OFFSETTYPE_DATASETWRITERID: - /* TODO */ - rv = DECODE_BINARY(&nm->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], UINT16); + rv = DECODE_BINARY(&dsm->dataSetWriterId, UINT16); break; case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: rv = DECODE_BINARY(&nm->groupHeader.sequenceNumber, UINT16); @@ -326,14 +325,11 @@ UA_PayloadHeader_encodeBinary(EncodeCtx *ctx, const UA_NetworkMessage* src) { if(src->networkMessageType != UA_NETWORKMESSAGE_DATASET) return UA_STATUSCODE_BADNOTIMPLEMENTED; - if(src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds == NULL) - return UA_STATUSCODE_BADENCODINGERROR; - - UA_Byte count = src->payloadHeader.dataSetPayloadHeader.count; + UA_Byte count = (UA_Byte)src->payload.dataSetPayload.dataSetMessagesSize; UA_StatusCode rv = UA_Byte_encodeBinary(&count, &ctx->pos, ctx->end); for(UA_Byte i = 0; i < count; i++) { - UA_UInt16 dswId = src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]; + UA_UInt16 dswId = src->payload.dataSetPayload.dataSetMessages[i].dataSetWriterId; rv |= UA_UInt16_encodeBinary(&dswId, &ctx->pos, ctx->end); } @@ -445,25 +441,15 @@ UA_NetworkMessage_encodePayload(const UA_NetworkMessage* src, UA_Byte **bufPos, ctx.pos = *bufPos; ctx.end = bufEnd; - UA_Byte count = 1; + UA_Byte count = (UA_Byte)src->payload.dataSetPayload.dataSetMessagesSize; UA_StatusCode rv; - if(src->payloadHeaderEnabled) { - count = src->payloadHeader.dataSetPayloadHeader.count; - if(count > 1) { - for(UA_Byte i = 0; i < count; i++) { - /* Calculate the size, if not specified */ - UA_UInt16 sz = 0; - if((src->payload.dataSetPayload.sizes != NULL) && - (src->payload.dataSetPayload.sizes[i] != 0)) { - sz = src->payload.dataSetPayload.sizes[i]; - } else { - UA_DataSetMessage *dsm = &src->payload.dataSetPayload.dataSetMessages[i]; - sz = (UA_UInt16)UA_DataSetMessage_calcSizeBinary(dsm, NULL, 0); - } - - rv = UA_UInt16_encodeBinary(&sz, &ctx.pos, ctx.end); - UA_CHECK_STATUS(rv, return rv); - } + if(src->payloadHeaderEnabled && count > 1) { + for(UA_Byte i = 0; i < count; i++) { + /* Calculate the size, if not specified */ + UA_DataSetMessage *dsm = &src->payload.dataSetPayload.dataSetMessages[i]; + UA_UInt16 sz = (UA_UInt16)UA_DataSetMessage_calcSizeBinary(dsm, NULL, 0); + rv = UA_UInt16_encodeBinary(&sz, &ctx.pos, ctx.end); + UA_CHECK_STATUS(rv, return rv); } } @@ -649,21 +635,22 @@ UA_PayloadHeader_decodeBinary(Ctx *ctx, UA_NetworkMessage* dst) { if(dst->networkMessageType != UA_NETWORKMESSAGE_DATASET) return UA_STATUSCODE_BADNOTIMPLEMENTED; - UA_DataSetPayloadHeader *h = &dst->payloadHeader.dataSetPayloadHeader; - UA_StatusCode rv = DECODE_BINARY(&h->count, BYTE); + UA_Byte count; + UA_StatusCode rv = DECODE_BINARY(&count, BYTE); UA_CHECK_STATUS(rv, return rv); - if(h->count == 0) + if(count == 0) return UA_STATUSCODE_GOOD; - h->dataSetWriterIds = (UA_UInt16 *)ctxCalloc(ctx, h->count, sizeof(UA_UInt16)); - if(!h->dataSetWriterIds) { - h->count = 0; + dst->payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*) + ctxCalloc(ctx, count, sizeof(UA_DataSetMessage)); + if(!dst->payload.dataSetPayload.dataSetMessages) return UA_STATUSCODE_BADOUTOFMEMORY; - } + dst->payload.dataSetPayload.dataSetMessagesSize = count; - for(UA_Byte i = 0; i < h->count; i++) { - rv |= DECODE_BINARY(&h->dataSetWriterIds[i], UINT16); + for(UA_Byte i = 0; i < count; i++) { + rv |= DECODE_BINARY(&dst->payload.dataSetPayload.dataSetMessages[i]. + dataSetWriterId, UINT16); } return rv; } @@ -813,42 +800,37 @@ UA_NetworkMessage_decodePayload(Ctx *ctx, UA_NetworkMessage *dst) { if(dst->networkMessageType != UA_NETWORKMESSAGE_DATASET) return UA_STATUSCODE_BADNOTIMPLEMENTED; - UA_StatusCode rv; - - /* This field shall be omitted if the payload-header is disabled or the count is 1 */ - UA_Byte count = 1; - if(dst->payloadHeaderEnabled) { - count = dst->payloadHeader.dataSetPayloadHeader.count; - if(count == 0) - return UA_STATUSCODE_BADDECODINGERROR; - if(count > 1) { - dst->payload.dataSetPayload.sizes = (UA_UInt16 *) - ctxCalloc(ctx, count, sizeof(UA_UInt16)); - UA_CHECK_MEM(dst->payload.dataSetPayload.sizes, - return UA_STATUSCODE_BADOUTOFMEMORY); - for(UA_Byte i = 0; i < count; i++) { - rv = DECODE_BINARY(&dst->payload.dataSetPayload.sizes[i], UINT16); - if(dst->payload.dataSetPayload.sizes[i] == 0) - return UA_STATUSCODE_BADDECODINGERROR; - UA_CHECK_STATUS(rv, return rv); - } - } + /* The dataset was already allocated if the header is enabled. + * To decode the DataSetReaderIds. */ + size_t count = dst->payload.dataSetPayload.dataSetMessagesSize; + if(!dst->payloadHeaderEnabled) { + count = 1; + dst->payload.dataSetPayload.dataSetMessages = + (UA_DataSetMessage *)ctxCalloc(ctx, 1, sizeof(UA_DataSetMessage)); + UA_CHECK_MEM(dst->payload.dataSetPayload.dataSetMessages, + return UA_STATUSCODE_BADOUTOFMEMORY); + dst->payload.dataSetPayload.dataSetMessagesSize = 1; } - dst->payload.dataSetPayload.dataSetMessages = - (UA_DataSetMessage *)ctxCalloc(ctx, count, sizeof(UA_DataSetMessage)); - UA_CHECK_MEM(dst->payload.dataSetPayload.dataSetMessages, - return UA_STATUSCODE_BADOUTOFMEMORY); - - if(count == 1) { - rv = UA_DataSetMessage_decodeBinary(ctx, dst->payload.dataSetPayload.dataSetMessages, 0); - } else { - for(UA_Byte i = 0; i < count; i++) { - rv = UA_DataSetMessage_decodeBinary(ctx, - &dst->payload.dataSetPayload.dataSetMessages[i], - dst->payload.dataSetPayload.sizes[i]); + /* Decode the payload sizes (for the raw encoding) */ + UA_StatusCode rv = UA_STATUSCODE_GOOD; + UA_STACKARRAY(UA_UInt16, payloadSizes, count); + memset(payloadSizes, 0, sizeof(UA_UInt16) * count); + if(count > 1) { + for(size_t i = 0; i < count; i++) { + rv |= DECODE_BINARY(&payloadSizes[i], UINT16); + if(payloadSizes[i] == 0) + return UA_STATUSCODE_BADDECODINGERROR; } } + UA_CHECK_STATUS(rv, return rv); + + /* Decode the DataSetMessages */ + for(size_t i = 0; i < count; i++) { + rv |= UA_DataSetMessage_decodeBinary(ctx, + &dst->payload.dataSetPayload.dataSetMessages[i], + payloadSizes[i]); + } return rv; } @@ -1020,8 +1002,6 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->payloadHeaderEnabled) { if(p->networkMessageType != UA_NETWORKMESSAGE_DATASET) return 0; /* not implemented */ - if(!p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds) - return 0; /* no dataSetWriterIds given! */ size += 1; /* p->payloadHeader.dataSetPayloadHeader.count */ if(offsetBuffer) { size_t pos = offsetBuffer->offsetsSize; @@ -1030,7 +1010,7 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( offsetBuffer->offsets[pos].offset = size; offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_DATASETWRITERID; } - size += (size_t)(2LU * p->payloadHeader.dataSetPayloadHeader.count); /* uint16 */ + size += (size_t)(2LU * p->payload.dataSetPayload.dataSetMessagesSize); /* uint16 */ } if(p->timestampEnabled) { @@ -1073,12 +1053,10 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( /* Encode the payload */ if(p->networkMessageType != UA_NETWORKMESSAGE_DATASET) return 0; /* not implemented */ - UA_Byte count = 1; - if(p->payloadHeaderEnabled) { - count = p->payloadHeader.dataSetPayloadHeader.count; - if(count > 1) - size += (size_t)(2LU * count); /* uint16 */ - } + + UA_Byte count = (UA_Byte)p->payload.dataSetPayload.dataSetMessagesSize; + if(p->payloadHeaderEnabled && count > 1) + size += (size_t)(2LU * count); /* DataSetMessagesSize (uint16) */ for(size_t i = 0; i < count; i++) { UA_DataSetMessage *dsm = &p->payload.dataSetPayload.dataSetMessages[i]; size = UA_DataSetMessage_calcSizeBinary(dsm, offsetBuffer, size); @@ -1098,17 +1076,8 @@ UA_NetworkMessage_clear(UA_NetworkMessage* p) { } if(p->networkMessageType == UA_NETWORKMESSAGE_DATASET) { - if(p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds && - p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds != UA_EMPTY_ARRAY_SENTINEL) - UA_free(p->payloadHeader.dataSetPayloadHeader.dataSetWriterIds); - - if(p->payload.dataSetPayload.sizes) - UA_free(p->payload.dataSetPayload.sizes); - if(p->payload.dataSetPayload.dataSetMessages) { - UA_Byte count = 1; - if(p->payloadHeaderEnabled) - count = p->payloadHeader.dataSetPayloadHeader.count; + UA_Byte count = (UA_Byte)p->payload.dataSetPayload.dataSetMessagesSize; for(size_t i = 0; i < count; i++) UA_DataSetMessage_clear(&p->payload.dataSetPayload.dataSetMessages[i]); UA_free(p->payload.dataSetPayload.dataSetMessages); diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index 544647b9e..d1dcc1c09 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -37,13 +37,12 @@ static UA_StatusCode writeJsonKey_UA_String(CtxJson *ctx, UA_String *in) { static UA_StatusCode UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, - UA_UInt16 dataSetWriterId, CtxJson *ctx) { status rv = writeJsonObjStart(ctx); /* DataSetWriterId */ rv |= writeJsonObjElm(ctx, UA_DECODEKEY_DATASETWRITERID, - &dataSetWriterId, &UA_TYPES[UA_TYPES_UINT16]); + &src->dataSetWriterId, &UA_TYPES[UA_TYPES_UINT16]); if(rv != UA_STATUSCODE_GOOD) return rv; @@ -165,27 +164,22 @@ UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx } /* Payload: DataSetMessages */ - UA_Byte count = src->payloadHeader.dataSetPayloadHeader.count; + size_t count = src->payload.dataSetPayload.dataSetMessagesSize; if(count > 0) { - UA_UInt16 *dataSetWriterIds = - src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds; - if(!dataSetWriterIds) - return UA_STATUSCODE_BADENCODINGERROR; - rv |= writeJsonKey(ctx, UA_DECODEKEY_MESSAGES); rv |= writeJsonArrStart(ctx); /* start array */ const UA_DataSetMessage *dataSetMessages = src->payload.dataSetPayload.dataSetMessages; - for(UA_UInt16 i = 0; i < count; i++) { + for(size_t i = 0; i < count; i++) { rv |= writeJsonBeforeElement(ctx, true); - rv |= UA_DataSetMessage_encodeJson_internal(&dataSetMessages[i], - dataSetWriterIds[i], ctx); + rv |= UA_DataSetMessage_encodeJson_internal(&dataSetMessages[i], ctx); if(rv != UA_STATUSCODE_GOOD) return rv; /* comma is needed if more dsm are present */ ctx->commaNeeded[ctx->depth] = true; } + rv |= writeJsonArrEnd(ctx, NULL); /* end array */ } @@ -380,12 +374,8 @@ static status DatasetMessage_Payload_decodeJsonInternal(ParseCtx *ctx, UA_DataSetMessage* dsm, const UA_DataType *type) { UA_ConfigurationVersionDataType cvd; - UA_UInt16 dataSetWriterId; - - dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; - DecodeEntry entries[6] = { - {UA_DECODEKEY_DATASETWRITERID, &dataSetWriterId, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, + {UA_DECODEKEY_DATASETWRITERID, &dsm->dataSetWriterId, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, {UA_DECODEKEY_SEQUENCENUMBER, &dsm->header.dataSetMessageSequenceNr, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, {UA_DECODEKEY_METADATAVERSION, &cvd, &MetaDataVersion_decodeJsonInternal, false, NULL}, {UA_DECODEKEY_TIMESTAMP, &dsm->header.timestamp, NULL, false, &UA_TYPES[UA_TYPES_DATETIME]}, @@ -398,15 +388,7 @@ DatasetMessage_Payload_decodeJsonInternal(ParseCtx *ctx, UA_DataSetMessage* dsm, if(ret != UA_STATUSCODE_GOOD || !entries[0].found || !entries[5].found) return UA_STATUSCODE_BADDECODINGERROR; - /* Set the DatasetWriterId in the context */ - if(!ctx->custom) - return UA_STATUSCODE_BADDECODINGERROR; - if(ctx->currentCustomIndex >= ctx->numCustom) - return UA_STATUSCODE_BADDECODINGERROR; - UA_UInt16* dataSetWriterIdsArray = (UA_UInt16*)ctx->custom; - dataSetWriterIdsArray[ctx->currentCustomIndex] = dataSetWriterId; - ctx->currentCustomIndex++; - + dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; dsm->header.dataSetMessageSequenceNrEnabled = entries[1].found; dsm->header.configVersionMajorVersion = cvd.majorVersion; dsm->header.configVersionMinorVersion = cvd.minorVersion; @@ -434,20 +416,13 @@ DatasetMessage_Array_decodeJsonInternal(ParseCtx *ctx, void *UA_RESTRICT dst, if(length == 0) return UA_STATUSCODE_GOOD; - /* Allocate memory */ - UA_DataSetMessage *dsm = (UA_DataSetMessage*) - UA_calloc(length, sizeof(UA_DataSetMessage)); - if(!dsm) - return UA_STATUSCODE_BADOUTOFMEMORY; + UA_DataSetMessage *dsm = (UA_DataSetMessage*)dst; - /* Copy new Pointer do dest */ - memcpy(dst, &dsm, sizeof(void*)); - - /* We go to first Array member! */ + /* Go to the first array member */ ctx->index++; - status ret = UA_STATUSCODE_BADDECODINGERROR; /* Decode array members */ + status ret = UA_STATUSCODE_BADDECODINGERROR; for(size_t i = 0; i < length; ++i) { ret = DatasetMessage_Payload_decodeJsonInternal(ctx, &dsm[i], NULL); if(ret != UA_STATUSCODE_GOOD) @@ -493,11 +468,6 @@ NetworkMessage_decodeJsonInternal(ParseCtx *ctx, UA_NetworkMessage *dst) { return UA_STATUSCODE_BADNOTIMPLEMENTED; size_t messageCount = (size_t)ctx->tokens[searchResultMessages].size; - /* Set up custom context for the dataSetwriterId */ - ctx->custom = (void*)UA_calloc(messageCount, sizeof(UA_UInt16)); - ctx->currentCustomIndex = 0; - ctx->numCustom = messageCount; - /* MessageType */ UA_Boolean isUaData = true; size_t searchResultMessageType = 0; @@ -522,6 +492,12 @@ NetworkMessage_decodeJsonInternal(ParseCtx *ctx, UA_NetworkMessage *dst) { if(!isUaData) return UA_STATUSCODE_BADNOTIMPLEMENTED; + dst->payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*) + UA_calloc(messageCount, sizeof(UA_DataSetMessage)); + if(!dst->payload.dataSetPayload.dataSetMessages) + return UA_STATUSCODE_BADOUTOFMEMORY; + dst->payload.dataSetPayload.dataSetMessagesSize = messageCount; + /* Network Message */ UA_String messageType; DecodeEntry entries[5] = { @@ -529,7 +505,7 @@ NetworkMessage_decodeJsonInternal(ParseCtx *ctx, UA_NetworkMessage *dst) { {UA_DECODEKEY_MESSAGETYPE, &messageType, NULL, false, NULL}, {UA_DECODEKEY_PUBLISHERID, &dst->publisherId, decodePublisherIdJsonInternal, false, NULL}, {UA_DECODEKEY_DATASETCLASSID, &dst->dataSetClassId, NULL, false, &UA_TYPES[UA_TYPES_GUID]}, - {UA_DECODEKEY_MESSAGES, &dst->payload.dataSetPayload.dataSetMessages, + {UA_DECODEKEY_MESSAGES, dst->payload.dataSetPayload.dataSetMessages, &DatasetMessage_Array_decodeJsonInternal, false, NULL} }; @@ -541,10 +517,7 @@ NetworkMessage_decodeJsonInternal(ParseCtx *ctx, UA_NetworkMessage *dst) { dst->publisherIdEnabled = entries[2].found; dst->dataSetClassIdEnabled = entries[3].found; dst->payloadHeaderEnabled = true; - dst->payloadHeader.dataSetPayloadHeader.count = (UA_Byte)messageCount; - /* Set the dataSetWriterIds. They are filled in the dataSet decoding. */ - dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16*)ctx->custom; return ret; } diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index c490f205c..01ddfe437 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -65,14 +65,15 @@ UA_DataSetReader_checkIdentifier(UA_PubSubManager *psm, UA_DataSetReader *dsr, UA_ReaderGroup *rg = dsr->linkedReaderGroup; if(rg->config.encodingMimeType == UA_PUBSUB_ENCODING_JSON) { - if(dsr->config.dataSetWriterId == - *msg->payloadHeader.dataSetPayloadHeader.dataSetWriterIds) { - return UA_STATUSCODE_GOOD; - } + // TODO + /* if(dsr->config.dataSetWriterId == */ + /* *msg->payloadHeader.dataSetPayloadHeader.dataSetWriterIds) { */ + /* return UA_STATUSCODE_GOOD; */ + /* } */ - UA_LOG_DEBUG_PUBSUB(psm->logging, dsr, "DataSetWriterId does not match. " - "Expected %u, received %u", dsr->config.dataSetWriterId, - *msg->payloadHeader.dataSetPayloadHeader.dataSetWriterIds); + /* UA_LOG_DEBUG_PUBSUB(psm->logging, dsr, "DataSetWriterId does not match. " */ + /* "Expected %u, received %u", dsr->config.dataSetWriterId, */ + /* *msg->payloadHeader.dataSetPayloadHeader.dataSetWriterIds); */ return UA_STATUSCODE_BADNOTFOUND; } @@ -86,9 +87,9 @@ UA_DataSetReader_checkIdentifier(UA_PubSubManager *psm, UA_DataSetReader *dsr, } if(msg->payloadHeaderEnabled) { - UA_Byte totalDataSets = msg->payloadHeader.dataSetPayloadHeader.count; + size_t totalDataSets = msg->payload.dataSetPayload.dataSetMessagesSize; for(size_t i = 0; i < totalDataSets; i++) { - UA_UInt32 dswId = msg->payloadHeader.dataSetPayloadHeader.dataSetWriterIds[i]; + UA_UInt32 dswId = msg->payload.dataSetPayload.dataSetMessages[i].dataSetWriterId; if(dsr->config.dataSetWriterId == dswId) return UA_STATUSCODE_GOOD; } diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index 8def6c7e3..97c87244a 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -530,9 +530,10 @@ UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg, } /* Process only the payloads where the WriterId from the header is expected */ - UA_DataSetPayloadHeader *ph = &nm->payloadHeader.dataSetPayloadHeader; - for(UA_Byte i = 0; i < ph->count; i++) { - if(reader->config.dataSetWriterId == ph->dataSetWriterIds[i]) { + size_t count = nm->payload.dataSetPayload.dataSetMessagesSize; + for(size_t i = 0; i < count; i++) { + if(reader->config.dataSetWriterId == nm->payload.dataSetPayload. + dataSetMessages[i].dataSetWriterId) { UA_DataSetReader_process(psm, reader, &nm->payload.dataSetPayload.dataSetMessages[i]); } diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index a5f195f6e..f9bcdac45 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -337,7 +337,7 @@ UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; res = UA_DataSetWriter_prepareDataSet(psm, dsw, &dsmStore[dsmCount]); if(res != UA_STATUSCODE_GOOD) - goto cleanup_dsm; + goto cleanup; dsmCount++; } @@ -347,7 +347,7 @@ UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { (UA_Byte) dsmCount, &wg->config.messageSettings, &wg->config.transportSettings, &networkMessage); if(res != UA_STATUSCODE_GOOD) - goto cleanup_dsm; + goto cleanup; /* Compute the message length and generate the offset-table (done inside * calcSizeBinary) */ @@ -423,9 +423,6 @@ UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { } cleanup: - UA_free(networkMessage.payload.dataSetPayload.sizes); - - cleanup_dsm: /* Clean up DataSetMessages */ for(size_t i = 0; i < dsmCount; i++) { UA_DataSetMessage_clear(&dsmStore[i]); @@ -740,12 +737,14 @@ sendNetworkMessageJson(UA_PubSubManager *psm, UA_PubSubConnection *connection, U nm.version = 1; nm.networkMessageType = UA_NETWORKMESSAGE_DATASET; nm.payloadHeaderEnabled = true; - nm.payloadHeader.dataSetPayloadHeader.count = dsmCount; - nm.payloadHeader.dataSetPayloadHeader.dataSetWriterIds = writerIds; nm.payload.dataSetPayload.dataSetMessages = dsm; + nm.payload.dataSetPayload.dataSetMessagesSize = dsmCount; nm.publisherIdEnabled = true; nm.publisherId = connection->config.publisherId; + for(size_t i = 0; i < dsmCount; i++) + nm.payload.dataSetPayload.dataSetMessages[i].dataSetWriterId = writerIds[i]; + /* Compute the message length */ size_t msgSize = UA_NetworkMessage_calcSizeJsonInternal(&nm, NULL, NULL, 0, true); @@ -863,20 +862,15 @@ generateNetworkMessage(UA_PubSubConnection *connection, UA_WriterGroup *wg, if(nm->groupHeader.groupVersionEnabled) nm->groupHeader.groupVersion = wgm->groupVersion; - /* Compute the length of the dsm separately for the header */ - UA_UInt16 *dsmLengths = (UA_UInt16 *) UA_calloc(dsmCount, sizeof(UA_UInt16)); - if(!dsmLengths) - return UA_STATUSCODE_BADOUTOFMEMORY; - for(UA_Byte i = 0; i < dsmCount; i++) - dsmLengths[i] = (UA_UInt16) UA_DataSetMessage_calcSizeBinary(&dsm[i], NULL, 0); - - nm->payloadHeader.dataSetPayloadHeader.count = dsmCount; - nm->payloadHeader.dataSetPayloadHeader.dataSetWriterIds = writerIds; nm->groupHeader.writerGroupId = wg->config.writerGroupId; /* number of the NetworkMessage inside a PublishingInterval */ nm->groupHeader.networkMessageNumber = 1; - nm->payload.dataSetPayload.sizes = dsmLengths; nm->payload.dataSetPayload.dataSetMessages = dsm; + nm->payload.dataSetPayload.dataSetMessagesSize = dsmCount; + + for(size_t i = 0; i < dsmCount; i++) + nm->payload.dataSetPayload.dataSetMessages[i].dataSetWriterId = writerIds[i]; + return UA_STATUSCODE_GOOD; } @@ -925,14 +919,11 @@ sendNetworkMessageBinary(UA_PubSubManager *psm, UA_PubSubConnection *connection, rv = encodeNetworkMessage(wg, &nm, &buf); if(rv != UA_STATUSCODE_GOOD) { cm->freeNetworkBuffer(cm, sendChannel, &buf); - UA_free(nm.payload.dataSetPayload.sizes); return rv; } /* Send out the message */ sendNetworkMessageBuffer(psm, wg, connection, sendChannel, &buf); - - UA_free(nm.payload.dataSetPayload.sizes); return UA_STATUSCODE_GOOD; } diff --git a/tests/pubsub/check_pubsub_encoding.c b/tests/pubsub/check_pubsub_encoding.c index bcc39cd1c..e7f3b9745 100644 --- a/tests/pubsub/check_pubsub_encoding.c +++ b/tests/pubsub/check_pubsub_encoding.c @@ -33,6 +33,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS1ValueVariantKeyFrame) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -94,6 +95,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS1ValueDataValueKeyFrame) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -161,6 +163,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesVariantKeyFrame) { dmkf.data.keyFrameData.dataSetFields[1].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -231,6 +234,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueKeyFrame) { dmkf.data.keyFrameData.dataSetFields[1].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -298,6 +302,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS1ValueVariantDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -361,6 +366,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS1ValueDataValueDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -421,6 +427,7 @@ START_TEST(UA_PubSub_Encode_WithBufferTooSmallShallReturnError) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -460,6 +467,7 @@ START_TEST(UA_PubSub_Decode_WithBufferTooSmallShallReturnError) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -527,6 +535,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesVariantDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[1].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -617,6 +626,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[1].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -706,6 +716,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesVariantKeyFrameGroupHeader) { dmkf.data.keyFrameData.dataSetFields[1].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -799,6 +810,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesVariantDeltaFramePublDSCID) { dmdf.data.deltaFrameData.deltaFrameFields[1].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -862,13 +874,11 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueKeyFramePH) { m.version = 1; m.networkMessageType = UA_NETWORKMESSAGE_DATASET; m.payloadHeaderEnabled = true; - m.payloadHeader.dataSetPayloadHeader.count = 1; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16 *)UA_Array_new(m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); - UA_UInt16 dataSetWriterId = 1698; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0] = dataSetWriterId; + UA_UInt16 dataSetWriterId = 1698; UA_DataSetMessage dmkf; memset(&dmkf, 0, sizeof(UA_DataSetMessage)); + dmkf.dataSetWriterId = dataSetWriterId; dmkf.header.dataSetMessageValid = true; dmkf.header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; dmkf.header.dataSetMessageType = UA_DATASETMESSAGE_DATAKEYFRAME; @@ -888,6 +898,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueKeyFramePH) { dmkf.data.keyFrameData.dataSetFields[1].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -905,8 +916,8 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueKeyFramePH) { ck_assert(m.networkMessageType == m2.networkMessageType); ck_assert(m.timestampEnabled == m2.timestampEnabled); ck_assert(m.payloadHeaderEnabled == m2.payloadHeaderEnabled); - ck_assert_uint_eq(m.payloadHeader.dataSetPayloadHeader.count, 1); - ck_assert_uint_eq(m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], dataSetWriterId); + ck_assert_uint_eq(m.payload.dataSetPayload.dataSetMessagesSize, 1); + ck_assert_uint_eq(m.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId, dataSetWriterId); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid == m2.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding == m2.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding); @@ -927,7 +938,6 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueKeyFramePH) { ck_assert(m.securityEnabled == m2.securityEnabled); ck_assert(m.chunkMessage == m2.chunkMessage); - UA_Array_delete(m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds, m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); UA_DataValue_clear(&dmkf.data.keyFrameData.dataSetFields[0]); UA_DataValue_clear(&dmkf.data.keyFrameData.dataSetFields[1]); UA_NetworkMessage_clear(&m2); @@ -971,6 +981,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesVariantKeyFrameTSProm) { dmkf.data.keyFrameData.dataSetFields[1].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1070,6 +1081,7 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueDeltaFrameGHProm2) { dmdf.data.deltaFrameData.deltaFrameFields[1].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1134,21 +1146,19 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn1DS2ValuesDataValueDeltaFrameGHProm2) { END_TEST START_TEST(UA_PubSub_EnDecode_ShallWorkOn2DSVariant) { + UA_UInt16 dsWriter1 = 4; + UA_UInt16 dsWriter2 = 7; UA_NetworkMessage m; memset(&m, 0, sizeof(UA_NetworkMessage)); m.version = 1; m.networkMessageType = UA_NETWORKMESSAGE_DATASET; m.payloadHeaderEnabled = true; - m.payloadHeader.dataSetPayloadHeader.count = 2; - UA_UInt16 dsWriter1 = 4; - UA_UInt16 dsWriter2 = 7; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16 *)UA_Array_new(m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0] = dsWriter1; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[1] = dsWriter2; - size_t memsize = m.payloadHeader.dataSetPayloadHeader.count * sizeof(UA_DataSetMessage); - m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*)UA_malloc(memsize); - memset(m.payload.dataSetPayload.dataSetMessages, 0, memsize); + m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*) + UA_calloc(2, sizeof(UA_DataSetMessage)); + m.payload.dataSetPayload.dataSetMessagesSize = 2; + m.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId = dsWriter1; + m.payload.dataSetPayload.dataSetMessages[1].dataSetWriterId = dsWriter2; //UA_DataSetMessage dmkf; //memset(&dmkf, 0, sizeof(UA_DataSetMessage)); @@ -1170,8 +1180,8 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn2DSVariant) { m.payload.dataSetPayload.dataSetMessages[1].header.dataSetMessageType = UA_DATASETMESSAGE_DATADELTAFRAME; UA_UInt16 fieldCountDS2 = 2; m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount = fieldCountDS2; - memsize = sizeof(UA_DataSetMessage_DeltaFrameField) * m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount; - m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields = (UA_DataSetMessage_DeltaFrameField*)UA_malloc(memsize); + m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields = (UA_DataSetMessage_DeltaFrameField*) + UA_calloc(m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount, sizeof(UA_DataSetMessage_DeltaFrameField)); UA_Guid gv = UA_Guid_random(); UA_UInt16 fieldIndex1 = 2; @@ -1210,8 +1220,8 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn2DSVariant) { ck_assert(m.securityEnabled == m2.securityEnabled); ck_assert(m.chunkMessage == m2.chunkMessage); ck_assert(m.payloadHeaderEnabled == m2.payloadHeaderEnabled); - ck_assert_uint_eq(m2.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], dsWriter1); - ck_assert_uint_eq(m2.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[1], dsWriter2); + ck_assert_uint_eq(m2.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId, dsWriter1); + ck_assert_uint_eq(m2.payload.dataSetPayload.dataSetMessages[1].dataSetWriterId, dsWriter2); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid == m2.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding == m2.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding); ck_assert_int_eq(m2.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.fieldCount, fieldCountDS1); @@ -1236,7 +1246,6 @@ START_TEST(UA_PubSub_EnDecode_ShallWorkOn2DSVariant) { ck_assert_int_eq(*(UA_Int64 *)m2.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields[1].fieldValue.value.data, iv64); ck_assert(m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields[1].fieldValue.hasSourceTimestamp == m2.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields[1].fieldValue.hasSourceTimestamp); - UA_Array_delete(m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds, m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); UA_Array_delete(m.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields, m.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.fieldCount, &UA_TYPES[UA_TYPES_DATAVALUE]); UA_free(m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields); UA_NetworkMessage_clear(&m2); diff --git a/tests/pubsub/check_pubsub_encoding_custom.c b/tests/pubsub/check_pubsub_encoding_custom.c index 1746e655e..5bc6b85dc 100644 --- a/tests/pubsub/check_pubsub_encoding_custom.c +++ b/tests/pubsub/check_pubsub_encoding_custom.c @@ -309,6 +309,7 @@ START_TEST(UA_PubSub_EnDecode_CustomScalarDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -381,6 +382,7 @@ START_TEST(UA_PubSub_EnDecode_CustomScalarKeyFrame) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -462,6 +464,7 @@ START_TEST(UA_PubSub_EnDecode_CustomScalarExtensionObjectDeltaFrame) { dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -540,6 +543,7 @@ START_TEST(UA_PubSub_EnDecode_CustomScalarExtensionObjectKeyFrame) { dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -616,6 +620,7 @@ START_TEST(UA_PubSub_EnDecode_CustomArrayDeltaFrame){ dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -679,6 +684,7 @@ START_TEST(UA_PubSub_EnDecode_CustomArrayKeyFrame){ dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -746,6 +752,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsDeltaFrame){ dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -824,6 +831,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsKeyFrame){ dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -900,6 +908,7 @@ START_TEST(UA_PubSub_EnDecode_CustomUnionDeltaFrame){ dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -971,6 +980,7 @@ START_TEST(UA_PubSub_EnDecode_CustomUnionKeyFrame){ dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1044,6 +1054,7 @@ START_TEST(UA_PubSub_EnDecode_SelfContainingUnionNormalMemberDeltaFrame){ dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1114,6 +1125,7 @@ START_TEST(UA_PubSub_EnDecode_SelfContainingUnionNormalMemberKeyFrame){ dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1192,6 +1204,7 @@ START_TEST(UA_PubSub_EnDecode_SelfContainingUnionSelfMemberDeltaFrame){ UA_free(s.fields.array.array); m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1271,6 +1284,7 @@ START_TEST(UA_PubSub_EnDecode_SelfContainingUnionSelfMemberKeyFrame){ UA_free(s.fields.array.array); m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1354,6 +1368,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsWithArrayNotConta dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1442,6 +1457,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsWithArrayNotConta dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1536,6 +1552,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsWithArrayContaine dmdf.data.deltaFrameData.deltaFrameFields[0].fieldValue.hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmdf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; @@ -1632,6 +1649,7 @@ START_TEST(UA_PubSub_EnDecode_CustomStructureWithOptionalFieldsWithArrayContaine dmkf.data.keyFrameData.dataSetFields[0].hasValue = true; m.payload.dataSetPayload.dataSetMessages = &dmkf; + m.payload.dataSetPayload.dataSetMessagesSize = 1; UA_StatusCode rv = UA_STATUSCODE_UNCERTAININITIALVALUE; UA_ByteString buffer; diff --git a/tests/pubsub/check_pubsub_encoding_json.c b/tests/pubsub/check_pubsub_encoding_json.c index ccfe3afb8..702dcd55e 100644 --- a/tests/pubsub/check_pubsub_encoding_json.c +++ b/tests/pubsub/check_pubsub_encoding_json.c @@ -15,19 +15,16 @@ #include START_TEST(UA_PubSub_EncodeAllOptionalFields) { + UA_UInt16 dsWriter1 = 12345; UA_NetworkMessage m; memset(&m, 0, sizeof(UA_NetworkMessage)); m.version = 1; m.networkMessageType = UA_NETWORKMESSAGE_DATASET; m.payloadHeaderEnabled = true; - m.payloadHeader.dataSetPayloadHeader.count = 1; - UA_UInt16 dsWriter1 = 12345; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16 *)UA_Array_new(m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0] = dsWriter1; - - size_t memsize = m.payloadHeader.dataSetPayloadHeader.count * sizeof(UA_DataSetMessage); - m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*)UA_malloc(memsize); - memset(m.payload.dataSetPayload.dataSetMessages, 0, memsize); + m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*) + UA_calloc(1, sizeof(UA_DataSetMessage)); + m.payload.dataSetPayload.dataSetMessagesSize = 1; + m.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId = dsWriter1; /* enable messageId */ m.messageIdEnabled = true; @@ -107,21 +104,18 @@ START_TEST(UA_PubSub_EncodeAllOptionalFields) { END_TEST START_TEST(UA_PubSub_EnDecode) { + UA_UInt16 dsWriter1 = 4; + UA_UInt16 dsWriter2 = 7; UA_NetworkMessage m; memset(&m, 0, sizeof(UA_NetworkMessage)); m.version = 1; m.networkMessageType = UA_NETWORKMESSAGE_DATASET; m.payloadHeaderEnabled = true; - m.payloadHeader.dataSetPayloadHeader.count = 2; - UA_UInt16 dsWriter1 = 4; - UA_UInt16 dsWriter2 = 7; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16 *)UA_Array_new(m.payloadHeader.dataSetPayloadHeader.count, &UA_TYPES[UA_TYPES_UINT16]); - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0] = dsWriter1; - m.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[1] = dsWriter2; - - size_t memsize = m.payloadHeader.dataSetPayloadHeader.count * sizeof(UA_DataSetMessage); - m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*)UA_malloc(memsize); - memset(m.payload.dataSetPayload.dataSetMessages, 0, memsize); + m.payload.dataSetPayload.dataSetMessages = (UA_DataSetMessage*) + UA_calloc(2, sizeof(UA_DataSetMessage)); + m.payload.dataSetPayload.dataSetMessagesSize = 2; + m.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId = dsWriter1; + m.payload.dataSetPayload.dataSetMessages[1].dataSetWriterId = dsWriter2; m.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid = true; m.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding = UA_FIELDENCODING_VARIANT; @@ -146,15 +140,14 @@ START_TEST(UA_PubSub_EnDecode) { m.payload.dataSetPayload.dataSetMessages[1].header.dataSetMessageType = UA_DATASETMESSAGE_DATAKEYFRAME; UA_UInt16 fieldCountDS2 = 2; m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.fieldCount = fieldCountDS2; - memsize = sizeof(UA_DataSetMessage_DeltaFrameField) * m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount; - m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields = (UA_DataSetMessage_DeltaFrameField*)UA_malloc(memsize); + m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.deltaFrameFields = (UA_DataSetMessage_DeltaFrameField*) + UA_calloc(m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount, sizeof(UA_DataSetMessage_DeltaFrameField)); /* Set fieldnames */ m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.fieldNames = (UA_String*)UA_Array_new(m.payload.dataSetPayload.dataSetMessages[1].data.deltaFrameData.fieldCount, &UA_TYPES[UA_TYPES_STRING]); m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.fieldNames[0] = UA_STRING_ALLOC("Field2.1"); m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.fieldNames[1] = UA_STRING_ALLOC("Field2.2"); - UA_Guid gv = UA_Guid_random(); UA_DataValue_init(&m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.dataSetFields[0]); UA_Variant_setScalarCopy(&m.payload.dataSetPayload.dataSetMessages[1].data.keyFrameData.dataSetFields[0].value, &gv, &UA_TYPES[UA_TYPES_GUID]); @@ -194,8 +187,8 @@ START_TEST(UA_PubSub_EnDecode) { ck_assert(m.securityEnabled == m2.securityEnabled); ck_assert(m.chunkMessage == m2.chunkMessage); ck_assert(m.payloadHeaderEnabled == m2.payloadHeaderEnabled); - ck_assert_uint_eq(m2.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], dsWriter1); - ck_assert_uint_eq(m2.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[1], dsWriter2); + ck_assert_uint_eq(m2.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId, dsWriter1); + ck_assert_uint_eq(m2.payload.dataSetPayload.dataSetMessages[1].dataSetWriterId, dsWriter2); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid == m2.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageValid); ck_assert(m.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding == m2.payload.dataSetPayload.dataSetMessages[0].header.fieldEncoding); ck_assert_int_eq(m2.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.fieldCount, fieldCountDS1); @@ -234,8 +227,8 @@ START_TEST(UA_NetworkMessage_oneMessage_twoFields_json_decode) { ck_assert_int_eq(out.publisherIdEnabled, false); ck_assert_int_eq(out.payloadHeaderEnabled, true); - ck_assert_int_eq(out.payloadHeader.dataSetPayloadHeader.count, 1); - ck_assert_int_eq(out.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], 62541); + ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessagesSize, 1); + ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId, 62541); //dataSetMessage ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageSequenceNrEnabled, true); @@ -295,8 +288,8 @@ START_TEST(UA_NetworkMessage_json_decode) { ck_assert_int_eq(out.publisherIdEnabled, false); ck_assert_int_eq(out.payloadHeaderEnabled, true); - ck_assert_int_eq(out.payloadHeader.dataSetPayloadHeader.count, 1); - ck_assert_int_eq(out.payloadHeader.dataSetPayloadHeader.dataSetWriterIds[0], 62541); + ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessagesSize, 1); + ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessages[0].dataSetWriterId, 62541); //dataSetMessage ck_assert_int_eq(out.payload.dataSetPayload.dataSetMessages[0].header.dataSetMessageSequenceNrEnabled, true); From 6a36b35739b5461d8af17d060a2adbe7f1c09873 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 20 Dec 2024 21:07:17 +0100 Subject: [PATCH 048/158] refactor(pubsub): Simplify the definition of UA_NetworkMessage --- include/open62541/pubsub.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/open62541/pubsub.h b/include/open62541/pubsub.h index 0b400d1e0..fdec13fc7 100644 --- a/include/open62541/pubsub.h +++ b/include/open62541/pubsub.h @@ -102,11 +102,6 @@ typedef enum { UA_NETWORKMESSAGE_DISCOVERY_RESPONSE = 2 } UA_NetworkMessageType; -typedef struct { - UA_DataSetMessage *dataSetMessages; - size_t dataSetMessagesSize; /* Goes into the payload header */ -} UA_DataSetPayload; - typedef struct { UA_Boolean writerGroupIdEnabled; UA_Boolean groupVersionEnabled; @@ -158,7 +153,11 @@ typedef struct { UA_NetworkMessageSecurityHeader securityHeader; union { - UA_DataSetPayload dataSetPayload; + struct { + UA_DataSetMessage *dataSetMessages; + size_t dataSetMessagesSize; /* Goes into the payload header */ + } dataSetPayload; + /* Extended with other payload types in the future */ } payload; UA_ByteString securityFooter; From b4d57830bb74656f3fcc0e1865a742a7ce7d2d5d Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 20 Dec 2024 22:12:25 +0100 Subject: [PATCH 049/158] refactor(core): Remove unused members from the JSON parsing context --- src/ua_types_encoding_json.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ua_types_encoding_json.h b/src/ua_types_encoding_json.h index 7ee086286..2cbed741d 100644 --- a/src/ua_types_encoding_json.h +++ b/src/ua_types_encoding_json.h @@ -68,12 +68,6 @@ typedef struct { const UA_String *serverUris; const UA_DataTypeArray *customTypes; - - /* Additonal data for special cases such as networkmessage/datasetmessage - * Currently only used for dataSetWriterIds */ - size_t numCustom; - void * custom; - size_t currentCustomIndex; } ParseCtx; typedef UA_StatusCode From 4f9c75e372493d04e114a1f0b1dc9bc99f90ef54 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 21 Dec 2024 19:33:24 +0100 Subject: [PATCH 050/158] refactor(pubsub): Add a clarifying comment for UA_DataSetMessage_calcSizeBinary --- src/pubsub/ua_pubsub_networkmessage_binary.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index b62ca1b02..89f76197b 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -1058,6 +1058,8 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->payloadHeaderEnabled && count > 1) size += (size_t)(2LU * count); /* DataSetMessagesSize (uint16) */ for(size_t i = 0; i < count; i++) { + /* size = ... as the original size is used as the starting point in + * UA_DataSetMessage_calcSizeBinary */ UA_DataSetMessage *dsm = &p->payload.dataSetPayload.dataSetMessages[i]; size = UA_DataSetMessage_calcSizeBinary(dsm, offsetBuffer, size); } From ba1810606fa5d82dcf9daece818b43098b9ebd1e Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 18 Dec 2024 11:10:39 +0100 Subject: [PATCH 051/158] feat(deps): Add lightweight utf8 de/encoding as a standalone implementation --- deps/README.md | 1 + deps/utf8.c | 67 ++++++++++++++++++++++++++++++++++++++++ deps/utf8.h | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 deps/utf8.c create mode 100644 deps/utf8.h diff --git a/deps/README.md b/deps/README.md index e47742bb7..95232b8f6 100644 --- a/deps/README.md +++ b/deps/README.md @@ -21,3 +21,4 @@ The following third party libraries may be included -- depending on the activate | mqtt-c | MIT | a portable MQTT client in C | | dtoa | BSL (Boost) | Printing of float numbers | | mp_printf | MIT | Our version of github:mpaland/printf | +| utf8 | MPL 2.0 | Lightweight utf8 de/encoding | diff --git a/deps/utf8.c b/deps/utf8.c new file mode 100644 index 000000000..ba6e0e582 --- /dev/null +++ b/deps/utf8.c @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include "utf8.h" + +#define UTF_PARSE_BYTE(pos) do { \ + byte = str[pos]; \ + if(UTF_UNLIKELY(byte < 0x80 || byte > 0xBF)) \ + return 0; /* Not a continuation byte */ \ + *codepoint = (*codepoint << 6) + (byte & 0x3F); \ + } while(0) + +unsigned +utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint) { + /* Ensure there is a character to read */ + if(UTF_UNLIKELY(len == 0)) + return 0; + + *codepoint = str[0]; + if(UTF_LIKELY(*codepoint < 0x80)) + return 1; /* Normal ASCII */ + + if(UTF_UNLIKELY(*codepoint <= 0xC1)) + return 0; /* Continuation byte not allowed here */ + + unsigned count; + unsigned char byte; + if(*codepoint <= 0xDF) { /* 2-byte sequence */ + if(len < 2) + return 0; + count = 2; + *codepoint &= 0x1F; + UTF_PARSE_BYTE(1); + if(UTF_UNLIKELY(*codepoint < 0x80)) + return 0; /* Too small for the encoding length */ + } else if(*codepoint <= 0xEF) { /* 3-byte sequence */ + if(len < 3) + return 0; + count = 3; + *codepoint &= 0xF; + UTF_PARSE_BYTE(1); + UTF_PARSE_BYTE(2); + if(UTF_UNLIKELY(*codepoint < 0x800)) + return 0; /* Too small for the encoding length */ + } else if(*codepoint <= 0xF4) { /* 4-byte sequence */ + if(len < 4) + return 0; + count = 4; + *codepoint &= 0x7; + UTF_PARSE_BYTE(1); + UTF_PARSE_BYTE(2); + UTF_PARSE_BYTE(3); + if(UTF_UNLIKELY(*codepoint < 0x10000)) + return 0; /* Too small for the encoding length */ + } else { + return 0; /* Invalid utf8 encoding */ + } + + if(UTF_UNLIKELY(*codepoint > 0x10FFFF)) + return 0; /* Not in the Unicode range */ + + return count; +} diff --git a/deps/utf8.h b/deps/utf8.h new file mode 100644 index 000000000..9ba0b539d --- /dev/null +++ b/deps/utf8.h @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#ifndef UTF8_H_ +#define UTF8_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _MSC_VER +# define UTF_INLINE __inline +#else +# define UTF_INLINE inline +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define UTF_LIKELY(x) __builtin_expect((x), 1) +# define UTF_UNLIKELY(x) __builtin_expect((x), 0) +#else +# define UTF_LIKELY(x) (x) +# define UTF_UNLIKELY(x) (x) +#endif + +/* Extract the next utf8 codepoint from the buffer. Returns the length (1-4) of + * the codepoint encoding or 0 upon an error. */ +unsigned +utf8_to_codepoint(const unsigned char *str, size_t len, unsigned *codepoint); + +/* Encodes the codepoint in utf8. The string needs to have enough space (at most + * four byte) available. Returns the encoding length. */ +static UTF_INLINE unsigned +utf8_from_codepoint(unsigned char *str, unsigned codepoint) { + if(UTF_LIKELY(codepoint <= 0x7F)) { /* Plain ASCII */ + str[0] = (unsigned char)codepoint; + return 1; + } + if(UTF_LIKELY(codepoint <= 0x07FF)) { /* 2-byte unicode */ + str[0] = (unsigned char)(((codepoint >> 6) & 0x1F) | 0xC0); + str[1] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80); + return 2; + } + if(UTF_LIKELY(codepoint <= 0xFFFF)) { /* 3-byte unicode */ + str[0] = (unsigned char)(((codepoint >> 12) & 0x0F) | 0xE0); + str[1] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80); + str[2] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80); + return 3; + } + if(UTF_LIKELY(codepoint <= 0x10FFFF)) { /* 4-byte unicode */ + str[0] = (unsigned char)(((codepoint >> 18) & 0x07) | 0xF0); + str[1] = (unsigned char)(((codepoint >> 12) & 0x3F) | 0x80); + str[2] = (unsigned char)(((codepoint >> 6) & 0x3F) | 0x80); + str[3] = (unsigned char)(((codepoint >> 0) & 0x3F) | 0x80); + return 4; + } + return 0; /* Not a unicode codepoint */ +} + +/* Returns the encoding length of the codepoint */ +static UTF_INLINE unsigned +utf8_length(unsigned codepoint) { + if(UTF_LIKELY(codepoint <= 0x7F)) + return 1; /* Plain ASCII */ + if(UTF_LIKELY(codepoint <= 0x07FF)) + return 2; /* 2-byte unicode */ + if(UTF_LIKELY(codepoint <= 0xFFFF)) + return 3; /* 3-byte unicode */ + if(UTF_LIKELY(codepoint <= 0x10FFFF)) + return 4; /* 4-byte unicode */ + return 0; /* Not a unicode codepoint */ +} + +#ifdef __cplusplus +} +#endif + +#endif /* UTF8_H_ */ From c29c0901f723cd6ea0893bef2ce73c43642c587c Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 19 Dec 2024 03:45:22 +0100 Subject: [PATCH 052/158] refactor(core): Use the utf8 decoding in /deps for JSON --- CMakeLists.txt | 2 + src/ua_types_encoding_json.c | 175 +++++++++++++---------------------- 2 files changed, 66 insertions(+), 111 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c19880926..ba75f2e0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -742,6 +742,7 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h ${PROJECT_SOURCE_DIR}/deps/base64.h ${PROJECT_SOURCE_DIR}/deps/dtoa.h ${PROJECT_SOURCE_DIR}/deps/mp_printf.h + ${PROJECT_SOURCE_DIR}/deps/utf8.h ${PROJECT_SOURCE_DIR}/deps/itoa.h ${PROJECT_SOURCE_DIR}/deps/ziptree.h ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.h @@ -812,6 +813,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c ${PROJECT_SOURCE_DIR}/deps/base64.c ${PROJECT_SOURCE_DIR}/deps/dtoa.c ${PROJECT_SOURCE_DIR}/deps/mp_printf.c + ${PROJECT_SOURCE_DIR}/deps/utf8.c ${PROJECT_SOURCE_DIR}/deps/itoa.c ${PROJECT_SOURCE_DIR}/deps/ziptree.c) diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index 539cefef2..b8200d295 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -16,6 +16,7 @@ #include #include +#include "../deps/utf8.h" #include "../deps/itoa.h" #include "../deps/dtoa.h" #include "../deps/parse_num.h" @@ -446,54 +447,9 @@ encodeJsonArray(CtxJson *ctx, const void *ptr, size_t length, return ret | writeJsonArrEnd(ctx, type); } -static const uint32_t min_codepoints[5] = {0x00, 0x00, 0x80, 0x800, 0x10000}; static const u8 hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; -/* Extract the next utf8 codepoint from the buffer. Return the next position in - * the buffer or NULL upon an error. */ -static const unsigned char * -extract_codepoint(const unsigned char *pos, size_t len, uint32_t *codepoint) { - UA_assert(len > 0); - - *codepoint = pos[0]; - if(UA_LIKELY(*codepoint < 0x80)) - return pos + 1; /* Normal ASCII */ - - if(UA_UNLIKELY(*codepoint <= 0xC1)) - return NULL; /* Continuation byte not allowed here */ - - unsigned char count; - if(*codepoint <= 0xDF) { - count = 2; /* 2-byte sequence */ - *codepoint &= 0x1F; - } else if(*codepoint <= 0xEF) { - count = 3; /* 3-byte sequence */ - *codepoint &= 0xF; - } else if(*codepoint <= 0xF4) { - count = 4; /* 4-byte sequence */ - *codepoint &= 0x7; - } else { - return NULL; /* invalid utf8 */ - } - - if(UA_UNLIKELY(count > len)) - return NULL; /* Not enough bytes left */ - - for(unsigned char i = 1; i < count; i++) { - unsigned char byte = pos[i]; - if(UA_UNLIKELY(byte < 0x80 || byte > 0xBF)) - return NULL; /* Not a continuation byte */ - *codepoint = (*codepoint << 6) + (byte & 0x3F); - } - - /* Not in Unicode range or too small for the encoding length */ - if(UA_UNLIKELY(*codepoint > 0x10FFFF || *codepoint < min_codepoints[count])) - return NULL; - - return pos + count; /* Return the new position in the pos */ -} - ENCODE_JSON(String) { if(!src->data) return writeChars(ctx, "null", 4); @@ -503,71 +459,65 @@ ENCODE_JSON(String) { UA_StatusCode ret = writeJsonQuote(ctx); - const unsigned char *str = src->data; - const unsigned char *pos = str; - const unsigned char *end = str; - const unsigned char *lim = str + src->length; - uint32_t codepoint = 0; - while(1) { - /* Iterate over codepoints in the utf8 encoding. Until the first - * character that needs to be escaped. */ - while(end < lim) { - end = extract_codepoint(pos, (size_t)(lim - pos), &codepoint); - if(!end) { - /* A malformed utf8 character. Print anyway and let the - * receiving side choose how to handle it. */ - pos++; - end = pos; - continue; - } - - /* Escape unprintable ASCII and escape characters */ - if(codepoint < ' ' || codepoint == 127 || - codepoint == '\\' || codepoint == '\"') + const unsigned char *pos = src->data; /* Input position */ + const unsigned char *end = pos + src->length; /* End of input */ + while(pos < end) { + /* Find the first escaped character */ + const unsigned char *start = pos; + for(; pos < end; pos++) { + if(*pos >= 127 || *pos < ' ' || *pos == '\\' || *pos == '\"') break; - - pos = end; } - /* Write out the characters that don't need escaping */ - if(pos != str) { - if(ctx->pos + (pos - str) > ctx->end) + /* Write out the unescaped ascii sequence */ + if(pos > start) { + if(ctx->pos + (pos - start) > ctx->end) return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; if(!ctx->calcOnly) - memcpy(ctx->pos, str, (size_t)(pos - str)); - ctx->pos += pos - str; + memcpy(ctx->pos, start, (size_t)(pos - start)); + ctx->pos += pos - start; } - /* Reached the end of the utf8 encoding */ - if(end == pos) + /* The unescaped ascii sequence reached the end */ + if(pos == end) break; - /* Handle an escaped character */ - size_t length = 2; - u8 seq[13]; - const char *text; + /* Parse an escaped character */ + unsigned codepoint = 0; + unsigned len = utf8_to_codepoint(pos, (size_t)(end - pos), &codepoint); + if(len == 0) { + /* A malformed utf8 character. Print anyway and let the + * receiving side choose how to handle it. */ + codepoint = *pos; + len = 1; + } + pos += len; + /* Write an escaped character */ + u8 escape_buf[13]; + const char *escape_text; + size_t escape_length = 2; switch(codepoint) { - case '\\': text = "\\\\"; break; - case '\"': text = "\\\""; break; - case '\b': text = "\\b"; break; - case '\f': text = "\\f"; break; - case '\n': text = "\\n"; break; - case '\r': text = "\\r"; break; - case '\t': text = "\\t"; break; + case '\\': escape_text = "\\\\"; break; + case '\"': escape_text = "\\\""; break; + case '\b': escape_text = "\\b"; break; + case '\f': escape_text = "\\f"; break; + case '\n': escape_text = "\\n"; break; + case '\r': escape_text = "\\r"; break; + case '\t': escape_text = "\\t"; break; default: - text = (char*)seq; + escape_text = (char*)escape_buf; if(codepoint < 0x10000) { /* codepoint is in BMP */ - seq[0] = '\\'; - seq[1] = 'u'; + escape_buf[0] = '\\'; + escape_buf[1] = 'u'; UA_Byte b1 = (UA_Byte)(codepoint >> 8u); UA_Byte b2 = (UA_Byte)(codepoint >> 0u); - seq[2] = hexmap[(b1 & 0xF0u) >> 4u]; - seq[3] = hexmap[b1 & 0x0Fu]; - seq[4] = hexmap[(b2 & 0xF0u) >> 4u]; - seq[5] = hexmap[b2 & 0x0Fu]; - length = 6; + escape_buf[2] = hexmap[(b1 & 0xF0u) >> 4u]; + escape_buf[3] = hexmap[b1 & 0x0Fu]; + escape_buf[4] = hexmap[(b2 & 0xF0u) >> 4u]; + escape_buf[5] = hexmap[b2 & 0x0Fu]; + escape_length = 6; } else { /* not in BMP -> construct a UTF-16 surrogate pair */ codepoint -= 0x10000; @@ -577,28 +527,31 @@ ENCODE_JSON(String) { UA_Byte fb2 = (UA_Byte)(first >> 0u); UA_Byte lb1 = (UA_Byte)(last >> 8u); UA_Byte lb2 = (UA_Byte)(last >> 0u); - seq[0] = '\\'; - seq[1] = 'u'; - seq[2] = hexmap[(fb1 & 0xF0u) >> 4u]; - seq[3] = hexmap[fb1 & 0x0Fu]; - seq[4] = hexmap[(fb2 & 0xF0u) >> 4u]; - seq[5] = hexmap[fb2 & 0x0Fu]; - seq[6] = '\\'; - seq[7] = 'u'; - seq[8] = hexmap[(lb1 & 0xF0u) >> 4u]; - seq[9] = hexmap[lb1 & 0x0Fu]; - seq[10] = hexmap[(lb2 & 0xF0u) >> 4u]; - seq[11] = hexmap[lb2 & 0x0Fu]; - length = 12; + escape_buf[0] = '\\'; + escape_buf[1] = 'u'; + escape_buf[2] = hexmap[(fb1 & 0xF0u) >> 4u]; + escape_buf[3] = hexmap[fb1 & 0x0Fu]; + escape_buf[4] = hexmap[(fb2 & 0xF0u) >> 4u]; + escape_buf[5] = hexmap[fb2 & 0x0Fu]; + escape_buf[6] = '\\'; + escape_buf[7] = 'u'; + escape_buf[8] = hexmap[(lb1 & 0xF0u) >> 4u]; + escape_buf[9] = hexmap[lb1 & 0x0Fu]; + escape_buf[10] = hexmap[(lb2 & 0xF0u) >> 4u]; + escape_buf[11] = hexmap[lb2 & 0x0Fu]; + escape_length = 12; } break; } - if(ctx->pos + length > ctx->end) + + /* Enough space? */ + if(ctx->pos + escape_length > ctx->end) return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + /* Write the escaped character */ if(!ctx->calcOnly) - memcpy(ctx->pos, text, length); - ctx->pos += length; - str = pos = end; + memcpy(ctx->pos, escape_text, escape_length); + ctx->pos += escape_length; } return ret | writeJsonQuote(ctx); From 02f2330525ef5a5b9df7e6a21370a08d9fa20c40 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 21 Dec 2024 21:05:13 +0100 Subject: [PATCH 053/158] refactor(deps): Reuse utf8.h in cj5.c --- deps/cj5.c | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/deps/cj5.c b/deps/cj5.c index a9d19107f..8bd167e4b 100644 --- a/deps/cj5.c +++ b/deps/cj5.c @@ -23,6 +23,7 @@ #include "cj5.h" #include "parse_num.h" +#include "utf8.h" #include #include @@ -700,6 +701,7 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index, case 'r': buf[outpos++] = '\r'; break; case 'n': buf[outpos++] = '\n'; break; case 't': buf[outpos++] = '\t'; break; + default: return CJ5_ERROR_INVALID; case 'u': { // Parse the unicode code point if(pos + 4 >= end) @@ -730,27 +732,12 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index, } // Write the utf8 bytes of the code point - if(utf <= 0x7F) { // Plain ASCII - buf[outpos++] = (char)utf; - } else if(utf <= 0x07FF) { // 2-byte unicode - buf[outpos++] = (char)(((utf >> 6) & 0x1F) | 0xC0); - buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80); - } else if(utf <= 0xFFFF) { // 3-byte unicode - buf[outpos++] = (char)(((utf >> 12) & 0x0F) | 0xE0); - buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80); - buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80); - } else if(utf <= 0x10FFFF) { // 4-byte unicode - buf[outpos++] = (char)(((utf >> 18) & 0x07) | 0xF0); - buf[outpos++] = (char)(((utf >> 12) & 0x3F) | 0x80); - buf[outpos++] = (char)(((utf >> 6) & 0x3F) | 0x80); - buf[outpos++] = (char)(((utf >> 0) & 0x3F) | 0x80); - } else { + unsigned len = utf8_from_codepoint((unsigned char*)buf + outpos, utf); + if(len == 0) return CJ5_ERROR_INVALID; // Not a utf8 string - } + outpos += len; break; } - default: - return CJ5_ERROR_INVALID; } continue; } @@ -759,7 +746,7 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index, // quotes if the quote character is not the same as the surrounding // quote character, e.g. 'this is my "quote"'. This logic is in the // token parsing code and not in this "string extraction" method. - if(c < ' ' || c == 127) + if(c < ' ' || c == 127) return CJ5_ERROR_INVALID; // Ascii character or utf8 byte From d186379f73fce7775f2664c732c5253bf2ef85b7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 22 Dec 2024 23:46:51 +0100 Subject: [PATCH 054/158] refactor(deps): Simplify parsing strings during JSON tokenization --- deps/cj5.c | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/deps/cj5.c b/deps/cj5.c index 8bd167e4b..14343332a 100644 --- a/deps/cj5.c +++ b/deps/cj5.c @@ -132,49 +132,16 @@ cj5__parse_string(cj5__parser *parser) { return; } - // Escape char + // Skip escape character if(c == '\\') { if(parser->pos + 1 >= len) { parser->error = CJ5_ERROR_INCOMPLETE; return; } parser->pos++; - switch(json5[parser->pos]) { - case '\"': - case '/': - case '\\': - case 'b': - case 'f': - case 'r': - case 'n': - case 't': - break; - case 'u': // The next four characters are an utf8 code - parser->pos++; - if(parser->pos + 4 >= len) { - parser->error = CJ5_ERROR_INVALID; - return; - } - for(unsigned int i = 0; i < 4; i++) { - // If it isn't a hex character we have an error - if(!(json5[parser->pos] >= 48 && json5[parser->pos] <= 57) && /* 0-9 */ - !(json5[parser->pos] >= 65 && json5[parser->pos] <= 70) && /* A-F */ - !(json5[parser->pos] >= 97 && json5[parser->pos] <= 102)) /* a-f */ - { - parser->error = CJ5_ERROR_INVALID; - return; - } - parser->pos++; - } - parser->pos--; - break; - case '\n': // Escape break line + if(json5[parser->pos] == '\n') { parser->line++; parser->line_start = parser->pos; - break; - default: - parser->error = CJ5_ERROR_INVALID; - return; } } } From 5f5875ab7e53bf9351091742aad06ca76e8bfb07 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 22 Dec 2024 23:47:11 +0100 Subject: [PATCH 055/158] refactor(deps): Simplify extracting a UTF8 string from the JSON token --- deps/cj5.c | 122 +++++++++++++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/deps/cj5.c b/deps/cj5.c index 14343332a..aaad90359 100644 --- a/deps/cj5.c +++ b/deps/cj5.c @@ -651,73 +651,67 @@ cj5_get_str(const cj5_result *r, unsigned int tok_index, unsigned int outpos = 0; for(; pos < end; pos++) { uint8_t c = (uint8_t)*pos; - - // Process an escape character - if(c == '\\') { - if(pos + 1 >= end) - return CJ5_ERROR_INCOMPLETE; - pos++; - c = (uint8_t)*pos; - switch(c) { - case '\"': buf[outpos++] = '\"'; break; - case '\\': buf[outpos++] = '\\'; break; - case '\n': buf[outpos++] = '\n'; break; // escape newline - case '/': buf[outpos++] = '/'; break; - case 'b': buf[outpos++] = '\b'; break; - case 'f': buf[outpos++] = '\f'; break; - case 'r': buf[outpos++] = '\r'; break; - case 'n': buf[outpos++] = '\n'; break; - case 't': buf[outpos++] = '\t'; break; - default: return CJ5_ERROR_INVALID; - case 'u': { - // Parse the unicode code point - if(pos + 4 >= end) - return CJ5_ERROR_INCOMPLETE; - pos++; - uint32_t utf; - cj5_error_code err = parse_codepoint(pos, &utf); - if(err != CJ5_ERROR_NONE) - return err; - pos += 3; - - if(0xD800 <= utf && utf <= 0xDBFF) { - // Parse a surrogate pair - if(pos + 6 >= end) - return CJ5_ERROR_INVALID; - if(pos[1] != '\\' && pos[3] != 'u') - return CJ5_ERROR_INVALID; - pos += 3; - uint32_t trail; - err = parse_codepoint(pos, &trail); - if(err != CJ5_ERROR_NONE) - return err; - pos += 3; - utf = (utf << 10) + trail + SURROGATE_OFFSET; - } else if(0xDC00 <= utf && utf <= 0xDFFF) { - // Invalid Unicode '\\u%04X' - return CJ5_ERROR_INVALID; - } - - // Write the utf8 bytes of the code point - unsigned len = utf8_from_codepoint((unsigned char*)buf + outpos, utf); - if(len == 0) - return CJ5_ERROR_INVALID; // Not a utf8 string - outpos += len; - break; - } - } - continue; - } - - // Unprintable ascii characters must be escaped. JSON5 allows nested - // quotes if the quote character is not the same as the surrounding - // quote character, e.g. 'this is my "quote"'. This logic is in the - // token parsing code and not in this "string extraction" method. + // Unprintable ascii characters must be escaped if(c < ' ' || c == 127) return CJ5_ERROR_INVALID; - // Ascii character or utf8 byte - buf[outpos++] = (char)c; + // Unescaped Ascii character or utf8 byte + if(c != '\\') { + buf[outpos++] = (char)c; + continue; + } + + // End of input before the escaped character + if(pos + 1 >= end) + return CJ5_ERROR_INCOMPLETE; + + // Process escaped character + pos++; + c = (uint8_t)*pos; + switch(c) { + case 'b': buf[outpos++] = '\b'; break; + case 'f': buf[outpos++] = '\f'; break; + case 'r': buf[outpos++] = '\r'; break; + case 'n': buf[outpos++] = '\n'; break; + case 't': buf[outpos++] = '\t'; break; + default: buf[outpos++] = c; break; + case 'u': { + // Parse a unicode code point + if(pos + 4 >= end) + return CJ5_ERROR_INCOMPLETE; + pos++; + uint32_t utf; + cj5_error_code err = parse_codepoint(pos, &utf); + if(err != CJ5_ERROR_NONE) + return err; + pos += 3; + + // Parse a surrogate pair + if(0xd800 <= utf && utf <= 0xdfff) { + if(pos + 6 >= end) + return CJ5_ERROR_INVALID; + if(pos[1] != '\\' && pos[2] != 'u') + return CJ5_ERROR_INVALID; + pos += 3; + uint32_t utf2; + err = parse_codepoint(pos, &utf2); + if(err != CJ5_ERROR_NONE) + return err; + pos += 3; + // High or low surrogate pair + utf = (utf <= 0xdbff) ? + (utf << 10) + utf2 + SURROGATE_OFFSET : + (utf2 << 10) + utf + SURROGATE_OFFSET; + } + + // Write the utf8 bytes of the code point + unsigned len = utf8_from_codepoint((unsigned char*)buf + outpos, utf); + if(len == 0) + return CJ5_ERROR_INVALID; // Not a utf8 string + outpos += len; + break; + } + } } // Terminate with \0 From 80f4e2be3b8f0afbe810f8dd3b975a66ca429afa Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 22 Dec 2024 23:47:29 +0100 Subject: [PATCH 056/158] refactor(core): Simplify printing UTF8 string to (escaped) JSON --- src/ua_types_encoding_json.c | 102 ++++++++++++----------------------- 1 file changed, 33 insertions(+), 69 deletions(-) diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index b8200d295..b5f8983c0 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -459,99 +459,63 @@ ENCODE_JSON(String) { UA_StatusCode ret = writeJsonQuote(ctx); - const unsigned char *pos = src->data; /* Input position */ - const unsigned char *end = pos + src->length; /* End of input */ - while(pos < end) { - /* Find the first escaped character */ + const unsigned char *end = src->data + src->length; + for(const unsigned char *pos = src->data; pos < end; pos++) { + /* Skip to the first character that needs escaping */ const unsigned char *start = pos; for(; pos < end; pos++) { - if(*pos >= 127 || *pos < ' ' || *pos == '\\' || *pos == '\"') + if(*pos < ' ' || *pos == 127 || *pos == '\\' || *pos == '\"') break; } - /* Write out the unescaped ascii sequence */ - if(pos > start) { - if(ctx->pos + (pos - start) > ctx->end) - return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; - if(!ctx->calcOnly) - memcpy(ctx->pos, start, (size_t)(pos - start)); - ctx->pos += pos - start; - } + /* Write out the unescaped sequence */ + if(ctx->pos + (pos - start) > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + if(!ctx->calcOnly) + memcpy(ctx->pos, start, (size_t)(pos - start)); + ctx->pos += pos - start; - /* The unescaped ascii sequence reached the end */ + /* The unescaped sequence reached the end */ if(pos == end) break; - /* Parse an escaped character */ - unsigned codepoint = 0; - unsigned len = utf8_to_codepoint(pos, (size_t)(end - pos), &codepoint); - if(len == 0) { - /* A malformed utf8 character. Print anyway and let the - * receiving side choose how to handle it. */ - codepoint = *pos; - len = 1; - } - pos += len; - /* Write an escaped character */ - u8 escape_buf[13]; - const char *escape_text; - size_t escape_length = 2; - switch(codepoint) { - case '\\': escape_text = "\\\\"; break; - case '\"': escape_text = "\\\""; break; - case '\b': escape_text = "\\b"; break; - case '\f': escape_text = "\\f"; break; - case '\n': escape_text = "\\n"; break; - case '\r': escape_text = "\\r"; break; - case '\t': escape_text = "\\t"; break; + char *escape_text; + char escape_buf[6]; + size_t escape_len = 2; + switch(*pos) { + case '\b': escape_text = "\\b"; break; + case '\f': escape_text = "\\f"; break; + case '\n': escape_text = "\\n"; break; + case '\r': escape_text = "\\r"; break; + case '\t': escape_text = "\\t"; break; default: - escape_text = (char*)escape_buf; - if(codepoint < 0x10000) { - /* codepoint is in BMP */ + escape_text = escape_buf; + if(*pos >= ' ' && *pos != 127) { + /* Escape \ or " */ escape_buf[0] = '\\'; - escape_buf[1] = 'u'; - UA_Byte b1 = (UA_Byte)(codepoint >> 8u); - UA_Byte b2 = (UA_Byte)(codepoint >> 0u); - escape_buf[2] = hexmap[(b1 & 0xF0u) >> 4u]; - escape_buf[3] = hexmap[b1 & 0x0Fu]; - escape_buf[4] = hexmap[(b2 & 0xF0u) >> 4u]; - escape_buf[5] = hexmap[b2 & 0x0Fu]; - escape_length = 6; + escape_buf[1] = *pos; } else { - /* not in BMP -> construct a UTF-16 surrogate pair */ - codepoint -= 0x10000; - UA_UInt32 first = 0xD800u | ((codepoint & 0xffc00u) >> 10u); - UA_UInt32 last = 0xDC00u | (codepoint & 0x003ffu); - UA_Byte fb1 = (UA_Byte)(first >> 8u); - UA_Byte fb2 = (UA_Byte)(first >> 0u); - UA_Byte lb1 = (UA_Byte)(last >> 8u); - UA_Byte lb2 = (UA_Byte)(last >> 0u); + /* Unprintable characters need to be escaped */ escape_buf[0] = '\\'; escape_buf[1] = 'u'; - escape_buf[2] = hexmap[(fb1 & 0xF0u) >> 4u]; - escape_buf[3] = hexmap[fb1 & 0x0Fu]; - escape_buf[4] = hexmap[(fb2 & 0xF0u) >> 4u]; - escape_buf[5] = hexmap[fb2 & 0x0Fu]; - escape_buf[6] = '\\'; - escape_buf[7] = 'u'; - escape_buf[8] = hexmap[(lb1 & 0xF0u) >> 4u]; - escape_buf[9] = hexmap[lb1 & 0x0Fu]; - escape_buf[10] = hexmap[(lb2 & 0xF0u) >> 4u]; - escape_buf[11] = hexmap[lb2 & 0x0Fu]; - escape_length = 12; + escape_buf[2] = '0'; + escape_buf[3] = '0'; + escape_buf[4] = hexmap[*pos >> 4]; + escape_buf[5] = hexmap[*pos & 0x0f]; + escape_len = 6; } break; } /* Enough space? */ - if(ctx->pos + escape_length > ctx->end) + if(ctx->pos + escape_len > ctx->end) return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; /* Write the escaped character */ if(!ctx->calcOnly) - memcpy(ctx->pos, escape_text, escape_length); - ctx->pos += escape_length; + memcpy(ctx->pos, escape_text, escape_len); + ctx->pos += escape_len; } return ret | writeJsonQuote(ctx); From edf5e8cf8cf1c3480a21bf19ed729d3eedbc8665 Mon Sep 17 00:00:00 2001 From: Thomas Nittel <144451640+ThomasNittel@users.noreply.github.com> Date: Mon, 23 Dec 2024 06:45:37 +0100 Subject: [PATCH 057/158] fix(examples): client_subscription_loop.c: Async functions used in callbacks (#6986) * Example client_subscription_loop.c: Async functions used in callbacks Async functions have to be used in callbacks else the error message "Cannot run EventLoop from the run method itself" occurs under Windows 10 with Microsoft Visual Studio 17 2022. * Example client_subscription_loop.c: Async functions used in callbacks Compile error "Unused variable" removed --- examples/client_subscription_loop.c | 77 ++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/examples/client_subscription_loop.c b/examples/client_subscription_loop.c index c58c02f65..ec8eb5914 100644 --- a/examples/client_subscription_loop.c +++ b/examples/client_subscription_loop.c @@ -50,6 +50,55 @@ subscriptionInactivityCallback (UA_Client *client, UA_UInt32 subId, void *subCon UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Inactivity for subscription %u", subId); } +static void +monCallback(UA_Client *client, void *userdata, + UA_UInt32 requestId, void *r) { + UA_CreateMonitoredItemsResponse *monResponse = (UA_CreateMonitoredItemsResponse *)r; + if(0 < monResponse->resultsSize && monResponse->results[0].statusCode == UA_STATUSCODE_GOOD) + { + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u", + monResponse->results[0].monitoredItemId); + } +} + +static void +createSubscriptionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *r) +{ + UA_CreateSubscriptionResponse *response = (UA_CreateSubscriptionResponse *)r; + if (response->subscriptionId == 0) + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "response->subscriptionId == 0, %u", response->subscriptionId); + else if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Create subscription failed, serviceResult %u", + response->responseHeader.serviceResult); + else + { + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Create subscription succeeded, id %u", + response->subscriptionId); + + /* Add a MonitoredItem */ + UA_NodeId currentTime = + UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); + UA_CreateMonitoredItemsRequest req; + UA_CreateMonitoredItemsRequest_init(&req); + UA_MonitoredItemCreateRequest monRequest = + UA_MonitoredItemCreateRequest_default(currentTime); + req.itemsToCreate = &monRequest; + req.itemsToCreateSize = 1; + req.subscriptionId = response->subscriptionId; + + UA_Client_DataChangeNotificationCallback dataChangeNotificationCallback[1] = { handler_currentTimeChanged }; + UA_StatusCode retval = + UA_Client_MonitoredItems_createDataChanges_async(client, req, NULL, dataChangeNotificationCallback, NULL, monCallback, NULL, NULL); + if (retval != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "UA_Client_MonitoredItems_createDataChanges_async ", UA_StatusCode_name(retval)); + } +} + static void stateCallback(UA_Client *client, UA_SecureChannelState channelState, UA_SessionState sessionState, UA_StatusCode recoveryStatus) { @@ -76,28 +125,12 @@ stateCallback(UA_Client *client, UA_SecureChannelState channelState, /* A new session was created. We need to create the subscription. */ /* Create a subscription */ UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default(); - UA_CreateSubscriptionResponse response = - UA_Client_Subscriptions_create(client, request, NULL, NULL, deleteSubscriptionCallback); - if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Create subscription succeeded, id %u", - response.subscriptionId); - else - return; - - /* Add a MonitoredItem */ - UA_NodeId currentTimeNode = UA_NS0ID(SERVER_SERVERSTATUS_CURRENTTIME); - UA_MonitoredItemCreateRequest monRequest = - UA_MonitoredItemCreateRequest_default(currentTimeNode); - - UA_MonitoredItemCreateResult monResponse = - UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId, - UA_TIMESTAMPSTORETURN_BOTH, monRequest, - NULL, handler_currentTimeChanged, NULL); - if(monResponse.statusCode == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Monitoring UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME', id %u", - monResponse.monitoredItemId); + UA_StatusCode retval = + UA_Client_Subscriptions_create_async(client, request, NULL, NULL, deleteSubscriptionCallback, + createSubscriptionCallback, NULL, NULL); + if (retval != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "UA_Client_Subscriptions_create_async ", UA_StatusCode_name(retval)); } break; case UA_SESSIONSTATE_CLOSED: From 6c00c70344db6dc9b7a8d0bbdd2471b8bb358852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Tyndel?= Date: Thu, 1 Feb 2024 13:46:50 +0100 Subject: [PATCH 058/158] fix(pubsub): fixed program crashing when sending too long strings Trying to send too long string would result in program crashing: when the string length exceeded maxStringLength, lengthDifference would underflow and we would try to zero out gigabytes of memory. Added check to avoid that. --- src/pubsub/ua_pubsub_networkmessage_binary.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index 89f76197b..387318717 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -1375,6 +1375,9 @@ UA_DataSetMessage_keyFrame_encodeBinary(const UA_DataSetMessage* src, UA_Byte ** if(fmd->maxStringLength != 0 && (v->value.type->typeKind == UA_DATATYPEKIND_STRING || v->value.type->typeKind == UA_DATATYPEKIND_BYTESTRING)) { + if(((UA_String *)v->value.data)->length > fmd->maxStringLength){ + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + } rv = UA_encodeBinaryInternal(valuePtr, v->value.type, bufPos, &bufEnd, NULL, NULL, NULL); size_t lengthDifference = fmd->maxStringLength - ((UA_String *)valuePtr)->length; From 38f3bc9bfb4f57314ac9bcbc521c303191787c5f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:52:28 +0200 Subject: [PATCH 059/158] feat(pubsub): Introduce a custom state machine callback in the PubSub component configuration --- include/open62541/server_pubsub.h | 44 ++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index b9bcf2d8b..12cae3597 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -181,6 +181,30 @@ typedef struct { UA_PubSubSecurityPolicy *securityPolicies; } UA_PubSubConfiguration; +/** + * PubSub Components + * ----------------- + * All PubSubComponents (Connection, Reader, ReaderGroup, ...) have a two + * configuration items in common: A void context-pointer and a callback to + * override the default state machine with a custom implementation. + * + * When a custom state machine is set, then internally no sockets are opened and + * no periodic callbacks are registered. All "active behavior" has to be + * managed/configured entirely in the custom state machine. */ + +/* The custom state machine callback is optional (can be NULL). It gets called + * with a request to change the state targetState. The state pointer contains + * the old (and afterwards the new) state. The notification stateChangeCallback + * is called afterwards. When a bad statuscode is returned, the component must + * be set to an ERROR state. */ +#define UA_PUBSUB_COMPONENT_CONTEXT \ + void *context; \ + UA_StatusCode (*customStateMachine)(UA_Server *server, \ + const UA_NodeId componentId, \ + void *componentContext, \ + UA_PubSubState *state, \ + UA_PubSubState targetState); \ + /* Enable all PubSubComponents. Returns the ORed statuscodes for enabling each * component individually. */ UA_EXPORT UA_StatusCode @@ -199,6 +223,7 @@ UA_Server_disableAllPubSubComponents(UA_Server *server); * runtime. */ typedef struct { + /* Configuration parameters from PubSubConnectionDataType */ UA_String name; UA_PublisherId publisherId; UA_String transportProfileUri; @@ -206,6 +231,8 @@ typedef struct { UA_KeyValueMap connectionProperties; UA_Variant connectionTransportSettings; + UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ + UA_EventLoop *eventLoop; /* Use an external EventLoop (use the EventLoop of * the server if this is NULL). Propagates to the * ReaderGroup/WriterGroup attached to the @@ -291,6 +318,9 @@ typedef struct { UA_PublishedEventConfig event; UA_PublishedEventTemplateConfig eventTemplate; } config; + + void *context; /* Context Configuration (PublishedDataSet has no state + * machine) */ } UA_PublishedDataSetConfig; void UA_EXPORT @@ -480,12 +510,15 @@ typedef struct { /* non std. field */ UA_PubSubRTLevel rtLevel; - /* Message are encrypted if a SecurityPolicy is configured and the + /* Security Configuration + * Message are encrypted if a SecurityPolicy is configured and the * securityMode set accordingly. The symmetric key is a runtime information * and has to be set via UA_Server_setWriterGroupEncryptionKey. */ UA_MessageSecurityMode securityMode; /* via the UA_WriterGroupDataType */ UA_PubSubSecurityPolicy *securityPolicy; UA_String securityGroupId; + + UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ } UA_WriterGroupConfig; void UA_EXPORT @@ -563,6 +596,8 @@ typedef struct { UA_ExtensionObject transportSettings; UA_String dataSetName; UA_KeyValueMap dataSetWriterProperties; + + UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ } UA_DataSetWriterConfig; void UA_EXPORT @@ -664,6 +699,9 @@ typedef struct { UA_TargetVariablesDataType target; } subscribedDataSet; UA_DataSetMetaDataType dataSetMetaData; + + void *context; /* Context Configuration (SubscribedDataSet has no state + * machine) */ } UA_SubscribedDataSetConfig; UA_EXPORT void @@ -719,6 +757,8 @@ typedef struct { /* non std. fields */ UA_String linkedStandaloneSubscribedDataSetName; UA_PubSubRtEncoding expectedEncoding; + + UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ } UA_DataSetReaderConfig; UA_EXPORT UA_StatusCode @@ -799,6 +839,8 @@ typedef struct { UA_MessageSecurityMode securityMode; UA_PubSubSecurityPolicy *securityPolicy; UA_String securityGroupId; + + UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ } UA_ReaderGroupConfig; void UA_EXPORT From b3397d6063ff9422829fe9597c4592ddbd2f2155 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:53:07 +0200 Subject: [PATCH 060/158] feat(pubsub): Enable the custom state machine for PubSubConnections --- src/pubsub/ua_pubsub_connection.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_connection.c b/src/pubsub/ua_pubsub_connection.c index b19c30b2f..37425bc7c 100644 --- a/src/pubsub/ua_pubsub_connection.c +++ b/src/pubsub/ua_pubsub_connection.c @@ -357,9 +357,20 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c UA_Boolean isTransient = c->head.transientState; c->head.transientState = true; + UA_Server *server = psm->sc.server; UA_StatusCode ret = UA_STATUSCODE_GOOD; UA_PubSubState oldState = c->head.state; + /* Custom state machine */ + if(c->config.customStateMachine) { + UA_UNLOCK(&server->serviceMutex); + ret = c->config.customStateMachine(server, c->head.identifier, c->config.context, + &c->head.state, targetState); + UA_LOCK(&server->serviceMutex); + goto finalize_state_machine; + } + + /* Internal state machine */ switch(targetState) { /* Disabled or Error */ case UA_PUBSUBSTATE_ERROR: @@ -405,6 +416,8 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c UA_PubSubConnection_disconnect(c); } + finalize_state_machine: + /* Only the top-level state update (if recursive calls are happening) * notifies the application and updates Reader and WriterGroups */ c->head.transientState = isTransient; @@ -413,15 +426,14 @@ UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c /* Inform application about state change */ if(c->head.state != oldState) { - UA_ServerConfig *config = &psm->sc.server->config; UA_LOG_INFO_PUBSUB(psm->logging, c, "%s -> %s", UA_PubSubState_name(oldState), UA_PubSubState_name(c->head.state)); - if(config->pubSubConfig.stateChangeCallback) { - UA_UNLOCK(&psm->sc.server->serviceMutex); - config->pubSubConfig. - stateChangeCallback(psm->sc.server, c->head.identifier, targetState, ret); - UA_LOCK(&psm->sc.server->serviceMutex); + if(server->config.pubSubConfig.stateChangeCallback) { + UA_UNLOCK(&server->serviceMutex); + server->config.pubSubConfig. + stateChangeCallback(server, c->head.identifier, targetState, ret); + UA_LOCK(&server->serviceMutex); } } From 35eee9ef595d6d5b566fcf71dd58f3d2f08b679b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:53:38 +0200 Subject: [PATCH 061/158] feat(pubsub): Enable the custom state machine for DataSetReaders --- src/pubsub/ua_pubsub_reader.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index 01ddfe437..34ee9fa27 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -358,8 +358,20 @@ UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, UA_ReaderGroup *rg = dsr->linkedReaderGroup; UA_assert(rg); + UA_Server *server = psm->sc.server; UA_PubSubState oldState = dsr->head.state; + /* Custom state machine */ + if(dsr->config.customStateMachine) { + UA_UNLOCK(&server->serviceMutex); + errorReason = dsr->config.customStateMachine(server, dsr->head.identifier, + dsr->config.context, + &dsr->head.state, targetState); + UA_LOCK(&server->serviceMutex); + goto finalize_state_machine; + } + + /* Internal state machine */ switch(targetState) { /* Disabled */ case UA_PUBSUBSTATE_DISABLED: @@ -394,17 +406,19 @@ UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, dsr->msgRcvTimeoutTimerId = 0; } + finalize_state_machine: + /* Inform application about state change */ if(dsr->head.state != oldState) { - UA_ServerConfig *config = &psm->sc.server->config; UA_LOG_INFO_PUBSUB(psm->logging, dsr, "%s -> %s", UA_PubSubState_name(oldState), UA_PubSubState_name(dsr->head.state)); - if(config->pubSubConfig.stateChangeCallback != 0) { - UA_UNLOCK(&psm->sc.server->serviceMutex); - config->pubSubConfig.stateChangeCallback(psm->sc.server, dsr->head.identifier, - dsr->head.state, errorReason); - UA_LOCK(&psm->sc.server->serviceMutex); + if(server->config.pubSubConfig.stateChangeCallback != 0) { + UA_UNLOCK(&server->serviceMutex); + server->config.pubSubConfig. + stateChangeCallback(server, dsr->head.identifier, + dsr->head.state, errorReason); + UA_LOCK(&server->serviceMutex); } } } From fb4f0268435dd70493a66fcf5b92947c4fc37789 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:54:03 +0200 Subject: [PATCH 062/158] feat(pubsub): Enable the custom state machine for ReaderGroups --- src/pubsub/ua_pubsub_readergroup.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index 97c87244a..fc501f7b2 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -348,10 +348,26 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_Boolean isTransient = rg->head.transientState; rg->head.transientState = true; + UA_Server *server = psm->sc.server; UA_StatusCode ret = UA_STATUSCODE_GOOD; UA_PubSubState oldState = rg->head.state; UA_PubSubConnection *connection = rg->linkedConnection; + /* Custom state machine */ + if(rg->config.customStateMachine) { + UA_UNLOCK(&server->serviceMutex); + ret = rg->config.customStateMachine(server, rg->head.identifier, rg->config.context, + &rg->head.state, targetState); + UA_LOCK(&server->serviceMutex); + if(rg->head.state == UA_PUBSUBSTATE_DISABLED || + rg->head.state == UA_PUBSUBSTATE_ERROR) + UA_ReaderGroup_unfreezeConfiguration(rg); + else + UA_ReaderGroup_freezeConfiguration(psm, rg); + goto finalize_state_machine; + } + + /* Internal state machine */ switch(targetState) { /* Disabled or Error */ case UA_PUBSUBSTATE_DISABLED: @@ -413,6 +429,8 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, rg->hasReceived = false; } + finalize_state_machine: + /* Only the top-level state update (if recursive calls are happening) * notifies the application and updates Reader and WriterGroups */ rg->head.transientState = isTransient; @@ -421,15 +439,14 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, /* Inform application about state change */ if(rg->head.state != oldState) { - UA_ServerConfig *pConfig = &psm->sc.server->config; UA_LOG_INFO_PUBSUB(psm->logging, rg, "%s -> %s", UA_PubSubState_name(oldState), UA_PubSubState_name(rg->head.state)); - if(pConfig->pubSubConfig.stateChangeCallback != 0) { - UA_UNLOCK(&psm->sc.server->serviceMutex); - pConfig->pubSubConfig. - stateChangeCallback(psm->sc.server, rg->head.identifier, rg->head.state, ret); - UA_LOCK(&psm->sc.server->serviceMutex); + if(server->config.pubSubConfig.stateChangeCallback != 0) { + UA_UNLOCK(&server->serviceMutex); + server->config.pubSubConfig. + stateChangeCallback(server, rg->head.identifier, rg->head.state, ret); + UA_LOCK(&server->serviceMutex); } } From 353c93bb0fb898341260ce459cd2d46560faba7c Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:54:18 +0200 Subject: [PATCH 063/158] feat(pubsub): Enable the custom state machine for WriterGroups --- src/pubsub/ua_pubsub_writergroup.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index f9bcdac45..e4316338e 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -535,10 +535,26 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, UA_Boolean isTransient = wg->head.transientState; wg->head.transientState = true; + UA_Server *server = psm->sc.server; UA_StatusCode ret = UA_STATUSCODE_GOOD; UA_PubSubState oldState = wg->head.state; UA_PubSubConnection *connection = wg->linkedConnection; + /* Custom state machine */ + if(wg->config.customStateMachine) { + UA_UNLOCK(&server->serviceMutex); + ret = wg->config.customStateMachine(server, wg->head.identifier, wg->config.context, + &wg->head.state, targetState); + UA_LOCK(&server->serviceMutex); + if(wg->head.state == UA_PUBSUBSTATE_DISABLED || + wg->head.state == UA_PUBSUBSTATE_ERROR) + UA_WriterGroup_unfreezeConfiguration(psm, wg); + else + UA_WriterGroup_freezeConfiguration(psm, wg); + goto finalize_state_machine; + } + + /* Internal state machine */ switch(targetState) { /* Disabled or Error */ case UA_PUBSUBSTATE_DISABLED: @@ -612,6 +628,8 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, UA_WriterGroup_removePublishCallback(psm, wg); } + finalize_state_machine: + /* Only the top-level state update (if recursive calls are happening) * notifies the application and updates Reader and WriterGroups */ wg->head.transientState = isTransient; @@ -620,15 +638,14 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, /* Inform the application about state change */ if(wg->head.state != oldState) { - UA_ServerConfig *pConfig = &psm->sc.server->config; UA_LOG_INFO_PUBSUB(psm->logging, wg, "%s -> %s", UA_PubSubState_name(oldState), UA_PubSubState_name(wg->head.state)); - if(pConfig->pubSubConfig.stateChangeCallback != 0) { - UA_UNLOCK(&psm->sc.server->serviceMutex); - pConfig->pubSubConfig. - stateChangeCallback(psm->sc.server, wg->head.identifier, wg->head.state, ret); - UA_LOCK(&psm->sc.server->serviceMutex); + if(server->config.pubSubConfig.stateChangeCallback != 0) { + UA_UNLOCK(&server->serviceMutex); + server->config.pubSubConfig. + stateChangeCallback(server, wg->head.identifier, wg->head.state, ret); + UA_LOCK(&server->serviceMutex); } } From 3dcdb93aa58cd4281ae98e7ad4b5581c171d97e7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sat, 12 Oct 2024 14:54:32 +0200 Subject: [PATCH 064/158] feat(pubsub): Enable the custom state machine for DataSetWriters --- src/pubsub/ua_pubsub_writer.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index d17ad2df0..e723fe06f 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -82,12 +82,27 @@ UA_DataSetWriter_unfreezeConfiguration(UA_DataSetWriter *dsw) { UA_StatusCode UA_DataSetWriter_setPubSubState(UA_PubSubManager *psm, UA_DataSetWriter *dsw, UA_PubSubState targetState) { + UA_Server *server = psm->sc.server; UA_StatusCode res = UA_STATUSCODE_GOOD; + UA_PubSubState oldState = dsw->head.state; UA_WriterGroup *wg = dsw->linkedWriterGroup; UA_assert(wg); - UA_PubSubState oldState = dsw->head.state; + /* Custom state machine */ + if(dsw->config.customStateMachine) { + UA_UNLOCK(&server->serviceMutex); + res = dsw->config.customStateMachine(server, dsw->head.identifier, dsw->config.context, + &dsw->head.state, targetState); + UA_LOCK(&server->serviceMutex); + if(dsw->head.state == UA_PUBSUBSTATE_DISABLED || + dsw->head.state == UA_PUBSUBSTATE_ERROR) + UA_DataSetWriter_unfreezeConfiguration(dsw); + else + UA_DataSetWriter_freezeConfiguration(dsw); + goto finalize_state_machine; + } + /* Internal state machine */ switch(targetState) { /* Disabled */ case UA_PUBSUBSTATE_DISABLED: @@ -117,9 +132,10 @@ UA_DataSetWriter_setPubSubState(UA_PubSubManager *psm, UA_DataSetWriter *dsw, break; } + finalize_state_machine: + /* Inform application about state change */ if(dsw->head.state != oldState) { - UA_Server *server = psm->sc.server; UA_LOG_INFO_PUBSUB(psm->logging, dsw, "%s -> %s", UA_PubSubState_name(oldState), UA_PubSubState_name(dsw->head.state)); From 994038146c7aafc64fb8ec1e6da13d4a08597cb8 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 17 Nov 2024 19:46:30 +0100 Subject: [PATCH 065/158] refactor(examples): Use the Custom State Machine in examples/pubsub_realtime/server_pubsub_publisher_rt_level.c --- .../server_pubsub_publisher_rt_level.c | 129 ++++++++++++------ 1 file changed, 85 insertions(+), 44 deletions(-) diff --git a/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c b/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c index 9417aa55f..c47343336 100644 --- a/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c +++ b/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c @@ -8,11 +8,15 @@ #include #include +#include +#include #define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100 #define PUBSUB_CONFIG_FIELD_COUNT 10 -UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; +static UA_Server *server; +static timer_t writerGroupTimer; +static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; /** * For realtime publishing the following is configured: @@ -47,36 +51,65 @@ valueUpdateCallback(UA_Server *server, void *data) { } } -/* Dedicated EventLoop for PubSub */ -volatile UA_Boolean pubSubELRunning = true; -UA_EventLoop *pubSubEL; +/* WriterGroup timer managed by a custom state machine. This uses + * UA_Server_triggerWriterGroupPublish. The server can block its internal mutex, + * so this can have some jitter. For hard realtime the publish callback has to + * send out the packet without going through the server. */ -static void * -runPubSubEL(void *_) { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGINT); - pthread_sigmask(SIG_BLOCK, &set, NULL); - while(pubSubELRunning) - pubSubEL->run(pubSubEL, 100); - return NULL; +static void +writerGroupPublishTrigger(union sigval signal) { + printf("XXX Publish Callback\n"); + UA_Server_triggerWriterGroupPublish(server, writerGroupIdent); +} + +static UA_StatusCode +writerGroupStateMachine(UA_Server *server, const UA_NodeId componentId, + void *componentContext, UA_PubSubState *state, + UA_PubSubState targetState) { + UA_WriterGroupConfig config; + struct itimerspec interval; + memset(&interval, 0, sizeof(interval)); + + if(targetState == *state) + return UA_STATUSCODE_GOOD; + + switch(targetState) { + /* Disabled or Error */ + case UA_PUBSUBSTATE_ERROR: + case UA_PUBSUBSTATE_DISABLED: + case UA_PUBSUBSTATE_PAUSED: + printf("XXX Disabling the WriterGroup\n"); + timer_settime(writerGroupTimer, 0, &interval, NULL); + *state = targetState; + break; + + /* Operational */ + case UA_PUBSUBSTATE_PREOPERATIONAL: + case UA_PUBSUBSTATE_OPERATIONAL: + if(*state == UA_PUBSUBSTATE_OPERATIONAL) + break; + printf("XXX Enabling the WriterGroup\n"); + UA_Server_getWriterGroupConfig(server, writerGroupIdent, &config); + interval.it_interval.tv_sec = config.publishingInterval / 1000; + interval.it_interval.tv_nsec = + ((long long)(config.publishingInterval * 1000 * 1000)) % (1000 * 1000 * 1000); + interval.it_value = interval.it_interval; + UA_WriterGroupConfig_clear(&config); + int res = timer_settime(writerGroupTimer, 0, &interval, NULL); + if(res != 0) + return UA_STATUSCODE_BADINTERNALERROR; + *state = UA_PUBSUBSTATE_OPERATIONAL; + break; + + /* Unknown state */ + default: + return UA_STATUSCODE_BADINTERNALERROR; + } + + return UA_STATUSCODE_GOOD; } int main(void) { - UA_Server *server = UA_Server_new(); - - /* Instantiate the custom EventLoop. - * Will be attached to the PubSubConnection and gets used for everything "above". - * This should be bound to a dedicated core for RT. */ - pubSubEL = UA_EventLoop_new_POSIX(UA_Log_Stdout); - UA_ConnectionManager *udpCM = - UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager")); - pubSubEL->registerEventSource(pubSubEL, (UA_EventSource *)udpCM); - pubSubEL->start(pubSubEL); - - pthread_t pubSubELThread; - pthread_create(&pubSubELThread, NULL, runPubSubEL, NULL); - /* Prepare the values */ for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { valueStore[i] = (UA_UInt32) i + 1; @@ -85,14 +118,26 @@ int main(void) { dvPointers[i] = &dvStore[i]; } + /* Initialize the timer */ + struct sigevent sigev; + memset(&sigev, 0, sizeof(sigev)); + sigev.sigev_notify = SIGEV_THREAD; + sigev.sigev_notify_function = writerGroupPublishTrigger; + timer_create(CLOCK_REALTIME, &sigev, &writerGroupTimer); + + /* Initialize the server */ + server = UA_Server_new(); + /* Add a PubSubConnection */ UA_PubSubConnectionConfig connectionConfig; memset(&connectionConfig, 0, sizeof(connectionConfig)); connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - connectionConfig.eventLoop = pubSubEL; - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.transportProfileUri = + UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + UA_NetworkAddressUrlDataType networkAddressUrl = + {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; connectionConfig.publisherId.id.uint16 = 2234; UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); @@ -122,14 +167,17 @@ int main(void) { writerGroupConfig.writerGroupId = 100; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + writerGroupConfig.customStateMachine = writerGroupStateMachine; /* Change message settings of writerGroup to send PublisherId, WriterGroupId * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ UA_UadpWriterGroupMessageDataType writerGroupMessage; UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) - (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); @@ -163,17 +211,10 @@ int main(void) { UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL, PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId); - UA_StatusCode retval = UA_Server_runUntilInterrupt(server); - - pubSubELRunning = false; - pthread_join(pubSubELThread, NULL); - - pubSubEL->stop(pubSubEL); - while(pubSubEL->state != UA_EVENTLOOPSTATE_STOPPED) - pubSubEL->run(pubSubEL, 0); - pubSubEL->free(pubSubEL); + UA_Server_runUntilInterrupt(server); + /* Cleanup */ UA_Server_delete(server); - - return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; + timer_delete(writerGroupTimer); + return EXIT_SUCCESS; } From c02a421a4481e9da865c07927623ae4e8ebdd00f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 17 Nov 2024 19:51:14 +0100 Subject: [PATCH 066/158] refactor(docs): Remove +x permission from opc-ua-tsn-wrs.pdf --- examples/pubsub_realtime/opc-ua-tsn-wrs.pdf | Bin 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 examples/pubsub_realtime/opc-ua-tsn-wrs.pdf diff --git a/examples/pubsub_realtime/opc-ua-tsn-wrs.pdf b/examples/pubsub_realtime/opc-ua-tsn-wrs.pdf old mode 100755 new mode 100644 From b2b6f520df5214bddb94d4888c77e107e1f2e085 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 17 Nov 2024 19:57:03 +0100 Subject: [PATCH 067/158] refactor(examples): Clean up PubSub RT examples --- examples/CMakeLists.txt | 7 +++---- .../{ => attic}/server_pubsub_rt_field_information_model.c | 0 ...b_publisher_rt_level.c => server_pubsub_publisher_rt.c} | 0 ...subscriber_rt_level.c => server_pubsub_subscriber_rt.c} | 0 tools/ci/examples_with_valgrind.py | 5 ++--- 5 files changed, 5 insertions(+), 7 deletions(-) rename examples/pubsub_realtime/{ => attic}/server_pubsub_rt_field_information_model.c (100%) rename examples/pubsub_realtime/{server_pubsub_publisher_rt_level.c => server_pubsub_publisher_rt.c} (100%) rename examples/pubsub_realtime/{server_pubsub_subscriber_rt_level.c => server_pubsub_subscriber_rt.c} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 62b80f795..baf594a11 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -208,14 +208,13 @@ add_subdirectory(nodeset) if(UA_ENABLE_PUBSUB) add_example(tutorial_pubsub_connection pubsub/tutorial_pubsub_connection.c) add_example(tutorial_pubsub_publish pubsub/tutorial_pubsub_publish.c) + add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) if(NOT WIN32) - add_example(server_pubsub_publish_rt_level pubsub_realtime/server_pubsub_publisher_rt_level.c) - add_example(server_pubsub_subscribe_rt_level pubsub_realtime/server_pubsub_subscriber_rt_level.c) - add_example(server_pubsub_rt_information_model pubsub_realtime/server_pubsub_rt_field_information_model.c) + add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publisher_rt.c) + add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscriber_rt.c) endif() - add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c) if (BUILD_SHARED_LIBS) message(WARNING "Build option BUILD_SHARED_LIBS not supported for standalone subscriber and realtime examples. Skipping these examples.") else (NOT BUILD_SHARED_LIBS) diff --git a/examples/pubsub_realtime/server_pubsub_rt_field_information_model.c b/examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_rt_field_information_model.c rename to examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c diff --git a/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c b/examples/pubsub_realtime/server_pubsub_publisher_rt.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_publisher_rt_level.c rename to examples/pubsub_realtime/server_pubsub_publisher_rt.c diff --git a/examples/pubsub_realtime/server_pubsub_subscriber_rt_level.c b/examples/pubsub_realtime/server_pubsub_subscriber_rt.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_subscriber_rt_level.c rename to examples/pubsub_realtime/server_pubsub_subscriber_rt.c diff --git a/tools/ci/examples_with_valgrind.py b/tools/ci/examples_with_valgrind.py index 7c19ad72a..941fa4b44 100644 --- a/tools/ci/examples_with_valgrind.py +++ b/tools/ci/examples_with_valgrind.py @@ -39,10 +39,9 @@ server_needed_examples = { "pubsub_subscribe_encrypted":"pubsub_publish_encrypted", "pubsub_subscribe_encrypted_sks":"server_pubsub_central_sks server_cert.der server_key.der --enableUnencrypted --enableAnonymous", "pubsub_subscribe_standalone_dataset":"tutorial_pubsub_publish", - "server_pubsub_publish_rt_level":"server_pubsub_subscribe_rt_level", - "server_pubsub_rt_information_model":"server_pubsub_subscribe_rt_level", "server_pubsub_subscribe_custom_monitoring":"server_pubsub_publish_on_demand", - "server_pubsub_subscribe_rt_level":"server_pubsub_publish_rt_level", + "server_pubsub_publish_rt":"server_pubsub_publish_rt", + "server_pubsub_subscribe_rt":"server_pubsub_subscribe_rt", "tutorial_client_events":"tutorial_server_events", "tutorial_client_firststeps":"tutorial_server_firststeps", "tutorial_pubsub_connection":"tutorial_pubsub_subscribe", From 5f72e47099f55cc683ca88acf3cd0dd4630c6a90 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 17 Nov 2024 20:08:05 +0100 Subject: [PATCH 068/158] refactor(pubsub): Make the ByteString in UA_PubSubConnection_process const --- src/pubsub/ua_pubsub_connection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pubsub/ua_pubsub_connection.c b/src/pubsub/ua_pubsub_connection.c index 37425bc7c..87a109e38 100644 --- a/src/pubsub/ua_pubsub_connection.c +++ b/src/pubsub/ua_pubsub_connection.c @@ -23,7 +23,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c, static void UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, - UA_ByteString msg); + const UA_ByteString msg); static void UA_PubSubConnection_disconnect(UA_PubSubConnection *c); @@ -276,7 +276,7 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) { static void UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, - UA_ByteString msg) { + const UA_ByteString msg) { UA_LOG_TRACE_PUBSUB(psm->logging, c, "Processing a received buffer"); /* Process RT ReaderGroups */ From 51c4b335b7a724893bb3f49e3bb9132b0394cf49 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 17 Nov 2024 20:14:21 +0100 Subject: [PATCH 069/158] feat(pubsub): Add UA_Server_processPubSubConnectionReceive --- include/open62541/server_pubsub.h | 8 ++++++++ src/pubsub/ua_pubsub_connection.c | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index 12cae3597..d49070626 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -259,6 +259,14 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE UA_Server_disablePubSubConnection(UA_Server *server, const UA_NodeId connectionId); +/* Manually "inject" a packet as if it had been received by the + * PubSubConnection. This is intended to be used in combination with a custom + * state machine where sockets (connections) are handled by user code. */ +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Server_processPubSubConnectionReceive(UA_Server *server, + const UA_NodeId connectionId, + const UA_ByteString packet); + /* Returns a deep copy of the config */ UA_StatusCode UA_EXPORT UA_THREADSAFE UA_Server_getPubSubConnectionConfig(UA_Server *server, diff --git a/src/pubsub/ua_pubsub_connection.c b/src/pubsub/ua_pubsub_connection.c index 87a109e38..031947ddd 100644 --- a/src/pubsub/ua_pubsub_connection.c +++ b/src/pubsub/ua_pubsub_connection.c @@ -947,4 +947,29 @@ UA_Server_disablePubSubConnection(UA_Server *server, const UA_NodeId cId) { return res; } +UA_StatusCode +UA_Server_processPubSubConnectionReceive(UA_Server *server, + const UA_NodeId connectionId, + const UA_ByteString packet) { + if(!server) + return UA_STATUSCODE_BADINTERNALERROR; + UA_LOCK(&server->serviceMutex); + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + UA_PubSubManager *psm = getPSM(server); + if(psm) { + UA_PubSubConnection *c = UA_PubSubConnection_find(psm, connectionId); + if(c) { + res = UA_STATUSCODE_GOOD; + UA_PubSubConnection_process(psm, c, packet); + } else { + res = UA_STATUSCODE_BADCONNECTIONCLOSED; + UA_LOG_WARNING_PUBSUB(psm->logging, c, + "Cannot process a packet if the " + "PubSubConnection is not operational"); + } + } + UA_UNLOCK(&server->serviceMutex); + return res; +} + #endif /* UA_ENABLE_PUBSUB */ From ccb0656677daa96b09c0d76d48d372cb856ae16d Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 18 Nov 2024 17:08:54 +0100 Subject: [PATCH 070/158] refactor(examples): Use the custom state machine logic in server_pubsub_subscriber_rt.c --- examples/CMakeLists.txt | 4 +- .../server_pubsub_subscriber_rt.c | 229 +++++++++++++++--- 2 files changed, 202 insertions(+), 31 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index baf594a11..3b4a58b24 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -211,9 +211,11 @@ if(UA_ENABLE_PUBSUB) add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) - if(NOT WIN32) + if(UA_ARCHITECTURE_POSIX) add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publisher_rt.c) + target_link_libraries(server_pubsub_publish_rt "rt") add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscriber_rt.c) + target_link_libraries(server_pubsub_subscribe_rt "rt") endif() if (BUILD_SHARED_LIBS) message(WARNING "Build option BUILD_SHARED_LIBS not supported for standalone subscriber and realtime examples. Skipping these examples.") diff --git a/examples/pubsub_realtime/server_pubsub_subscriber_rt.c b/examples/pubsub_realtime/server_pubsub_subscriber_rt.c index 8e090c267..29bfde509 100644 --- a/examples/pubsub_realtime/server_pubsub_subscriber_rt.c +++ b/examples/pubsub_realtime/server_pubsub_subscriber_rt.c @@ -11,9 +11,22 @@ #include #include #include +#include + +#include +#include +#include +#include +#include +#include #define PUBSUB_CONFIG_FIELD_COUNT 10 +static UA_NetworkAddressUrlDataType networkAddressUrl = + {{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")}; +static UA_String transportProfile = + UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + /** * The main target of this example is to reduce the time spread and effort * during the subscribe cycle. This RT level example is based on buffered @@ -31,8 +44,170 @@ UA_NodeId connectionIdentifier; UA_NodeId readerGroupIdentifier; UA_NodeId readerIdentifier; +UA_Server *server; +pthread_t listenThread; +int listenSocket; + UA_DataSetReaderConfig readerConfig; +static void * +listenUDP(void *_) { + (void)_; + + /* Block SIGINT for correct shutdown via the main thread */ + sigset_t blockset; + sigemptyset(&blockset); + sigaddset(&blockset, SIGINT); + sigprocmask(SIG_BLOCK, &blockset, NULL); + + /* Extract the hostname */ + UA_UInt16 port = 0; + UA_String hostname = UA_STRING_NULL; + UA_parseEndpointUrl(&networkAddressUrl.url, &hostname, &port, NULL); + + /* Get all the interface and IPv4/6 combinations for the configured hostname */ + struct addrinfo hints, *info; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */ + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE; + + /* getaddrinfo */ + char portstr[6]; + char hostnamebuf[256]; + snprintf(portstr, 6, "%d", port); + memcpy(hostnamebuf, hostname.data, hostname.length); + hostnamebuf[hostname.length] = 0; + int result = getaddrinfo(hostnamebuf, portstr, &hints, &info); + if(result != 0) { + printf("XXX getaddrinfo failed\n"); + return NULL; + } + + /* Open the socket */ + listenSocket = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + if(listenSocket <= 0) { + printf("XXX Cannot create the socket\n"); + return NULL; + } + + /* Set socket options */ + int opts = fcntl(listenSocket, F_GETFL); + result |= fcntl(listenSocket, F_SETFL, opts | O_NONBLOCK); + int optval = 1; + result |= setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, + (const char*)&optval, sizeof(optval)); + if(result < 0) { + printf("XXX Cannot set the socket options\n"); + return NULL; + } + + /* Bind the socket */ + result = bind(listenSocket, info->ai_addr, (socklen_t)info->ai_addrlen); + if(result < 0) { + printf("XXX Cannot bind the socket\n"); + return NULL; + } + + /* Join the multicast group */ + if(info->ai_family == AF_INET) { + struct ip_mreqn ipv4; + struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr; + ipv4.imr_multiaddr = sin->sin_addr; + ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */ + ipv4.imr_ifindex = 0; + result = setsockopt(listenSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipv4, sizeof(ipv4)); + } else if(info->ai_family == AF_INET6) { + struct ipv6_mreq ipv6; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)info->ai_addr; + ipv6.ipv6mr_multiaddr = sin6->sin6_addr; + ipv6.ipv6mr_interface = 0; /* default ANY interface */ + result = setsockopt(listenSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6, sizeof(ipv6)); + } + if(result < 0) { + printf("XXX Cannot join the multicast group\n"); + return NULL; + } + + freeaddrinfo(info); + + /* The connection is open, change the state to OPERATIONAL. + * The state machine checks whether listenSocket != 0. */ + printf("XXX Listening on UDP multicast (%s, port %u)\n", + hostnamebuf, (unsigned)port); + UA_Server_enablePubSubConnection(server, connectionIdentifier); + + /* Poll and process in a loop. + * The socket is closed in the state machine and */ + struct pollfd pfd; + pfd.fd = listenSocket; + pfd.events = POLLIN; + while(true) { + result = poll(&pfd, 1, -1); /* infinite timeout */ + if(pfd.revents & POLLERR || pfd.revents & POLLHUP || pfd.revents & POLLNVAL) + break; + + if(pfd.revents & POLLIN) { + static char buf[1024]; + ssize_t size = read(listenSocket, buf, sizeof(buf)); + if(size > 0) { + printf("XXX Received a packet\n"); + UA_ByteString packet = {(size_t)size, (UA_Byte*)buf}; + UA_Server_processPubSubConnectionReceive(server, connectionIdentifier, packet); + } + } + } + + printf("XXX The UDP multicast connection is closed\n"); + + /* Clean up and notify the state machine */ + close(listenSocket); + listenSocket = 0; + UA_Server_disablePubSubConnection(server, connectionIdentifier); + return NULL; +} + +static UA_StatusCode +connectionStateMachine(UA_Server *server, const UA_NodeId componentId, + void *componentContext, UA_PubSubState *state, + UA_PubSubState targetState) { + if(targetState == *state) + return UA_STATUSCODE_GOOD; + + switch(targetState) { + /* Disabled or Error */ + case UA_PUBSUBSTATE_ERROR: + case UA_PUBSUBSTATE_DISABLED: + case UA_PUBSUBSTATE_PAUSED: + printf("XXX Closing the UDP multicast connection\n"); + if(listenSocket != 0) + shutdown(listenSocket, SHUT_RDWR); + *state = targetState; + break; + + /* Operational */ + case UA_PUBSUBSTATE_PREOPERATIONAL: + case UA_PUBSUBSTATE_OPERATIONAL: + if(listenSocket != 0) { + *state = UA_PUBSUBSTATE_OPERATIONAL; + break; + } + printf("XXX Opening the UDP multicast connection\n"); + *state = UA_PUBSUBSTATE_PREOPERATIONAL; + int res = pthread_create(&listenThread, NULL, listenUDP, NULL); + if(res != 0) + return UA_STATUSCODE_BADINTERNALERROR; + break; + + /* Unknown state */ + default: + return UA_STATUSCODE_BADINTERNALERROR; + } + + return UA_STATUSCODE_GOOD; +} + /* Simulate a custom data sink (e.g. shared memory) */ UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; @@ -133,17 +308,17 @@ fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { /* Add new connection to the server */ static void -addPubSubConnection(UA_Server *server, UA_String *transportProfile, - UA_NetworkAddressUrlDataType *networkAddressUrl) { +addPubSubConnection(UA_Server *server) { /* Configuration creation for the connection */ UA_PubSubConnectionConfig connectionConfig; memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); connectionConfig.name = UA_STRING("UDPMC Connection 1"); - connectionConfig.transportProfileUri = *transportProfile; - UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl, + connectionConfig.transportProfileUri = transportProfile; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); + connectionConfig.customStateMachine = connectionStateMachine; UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); } @@ -285,31 +460,7 @@ addDataSetReader(UA_Server *server) { UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); } -static int -run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) { - /* Return value initialized to Status Good */ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - UA_Server *server = UA_Server_new(); - - addPubSubConnection(server, transportProfile, networkAddressUrl); - addReaderGroup(server); - addDataSetReader(server); - - UA_Server_enableAllPubSubComponents(server); - retval = UA_Server_runUntilInterrupt(server); - - UA_Server_delete(server); - - for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - UA_DataValue_delete(repeatedDataValueRT[i]); - } - - return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; -} - int main(int argc, char **argv) { - UA_String transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; if(argc > 1) { if(strcmp(argv[1], "-h") == 0) { printf("usage: %s [device]\n", argv[0]); @@ -330,9 +481,27 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } } - if (argc > 2) { + if(argc > 2) networkAddressUrl.networkInterface = UA_STRING(argv[2]); + + /* Return value initialized to Status Good */ + UA_StatusCode retval = UA_STATUSCODE_GOOD; + server = UA_Server_new(); + + addPubSubConnection(server); + addReaderGroup(server); + addDataSetReader(server); + + UA_Server_enableAllPubSubComponents(server); + retval = UA_Server_runUntilInterrupt(server); + + UA_Server_delete(server); + + pthread_join(listenThread, NULL); + + for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + UA_DataValue_delete(repeatedDataValueRT[i]); } - return run(&transportProfile, &networkAddressUrl); + return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } From 5cba8cb3cb6c6bc76dd66e57f52ebf724d69df97 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 19 Dec 2024 07:11:49 +0100 Subject: [PATCH 071/158] feat(tests): Add unit test for the PubSub custom state machine --- tests/CMakeLists.txt | 4 + .../check_pubsub_custom_state_machine.c | 664 ++++++++++++++++++ 2 files changed, 668 insertions(+) create mode 100644 tests/pubsub/check_pubsub_custom_state_machine.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 146fdc29f..4f401eeef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -277,6 +277,10 @@ if(UA_ENABLE_PUBSUB) ua_add_test(pubsub/check_pubsub_subscribe_rt_levels.c) ua_add_test(pubsub/check_pubsub_multiple_subscribe_rt_levels.c) + if(UA_ARCHITECTURE_POSIX) + ua_add_test(pubsub/check_pubsub_custom_state_machine.c) + endif() + if(UA_ENABLE_ENCRYPTION_MBEDTLS) ua_add_test(pubsub/check_pubsub_encryption.c) ua_add_test(pubsub/check_pubsub_encryption_aes256.c) diff --git a/tests/pubsub/check_pubsub_custom_state_machine.c b/tests/pubsub/check_pubsub_custom_state_machine.c new file mode 100644 index 000000000..0facdd3fa --- /dev/null +++ b/tests/pubsub/check_pubsub_custom_state_machine.c @@ -0,0 +1,664 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2014 Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include +#include + +#include "test_helpers.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100 +#define PUBSUB_CONFIG_FIELD_COUNT 10 + +static int listenSocket; +static UA_Server *server; +static pthread_t listenThread; +static timer_t writerGroupTimer; +static UA_DataSetReaderConfig readerConfig; +static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, + connectionIdentifier, readerGroupIdentifier, readerIdentifier; + +static void setup(void) { + server = UA_Server_newForUnitTest(); + ck_assert(server != NULL); + UA_Server_run_startup(server); +} + +static UA_NetworkAddressUrlDataType networkAddressUrl = + {{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")}; +static UA_String transportProfile = + UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + +/* Values in static locations. We cycle the dvPointers double-pointer to the + * next with atomic operations. */ +UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT]; + +static void +valueUpdateCallback(UA_Server *server, void *data) { + for(int i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; ++i) { + if(dvPointers[i] < &dvStore[PUBSUB_CONFIG_FIELD_COUNT - 1]) + UA_atomic_xchg((void**)&dvPointers[i], dvPointers[i]+1); + else + UA_atomic_xchg((void**)&dvPointers[i], &dvStore[0]); + } +} + +/* WriterGroup timer managed by a custom state machine. This uses + * UA_Server_triggerWriterGroupPublish. The server can block its internal mutex, + * so this can have some jitter. For hard realtime the publish callback has to + * send out the packet without going through the server. */ + +static void +writerGroupPublishTrigger(union sigval signal) { + printf("XXX Publish Callback\n"); + UA_Server_triggerWriterGroupPublish(server, writerGroupIdent); +} + +static UA_StatusCode +writerGroupStateMachine(UA_Server *server, const UA_NodeId componentId, + void *componentContext, UA_PubSubState *state, + UA_PubSubState targetState) { + UA_WriterGroupConfig config; + struct itimerspec interval; + memset(&interval, 0, sizeof(interval)); + + if(targetState == *state) + return UA_STATUSCODE_GOOD; + + switch(targetState) { + /* Disabled or Error */ + case UA_PUBSUBSTATE_ERROR: + case UA_PUBSUBSTATE_DISABLED: + case UA_PUBSUBSTATE_PAUSED: + printf("XXX Disabling the WriterGroup\n"); + timer_settime(writerGroupTimer, 0, &interval, NULL); + *state = targetState; + break; + + /* Operational */ + case UA_PUBSUBSTATE_PREOPERATIONAL: + case UA_PUBSUBSTATE_OPERATIONAL: + if(*state == UA_PUBSUBSTATE_OPERATIONAL) + break; + printf("XXX Enabling the WriterGroup\n"); + UA_Server_getWriterGroupConfig(server, writerGroupIdent, &config); + interval.it_interval.tv_sec = config.publishingInterval / 1000; + interval.it_interval.tv_nsec = + ((long long)(config.publishingInterval * 1000 * 1000)) % (1000 * 1000 * 1000); + interval.it_value = interval.it_interval; + UA_WriterGroupConfig_clear(&config); + int res = timer_settime(writerGroupTimer, 0, &interval, NULL); + if(res != 0) + return UA_STATUSCODE_BADINTERNALERROR; + *state = UA_PUBSUBSTATE_OPERATIONAL; + break; + + /* Unknown state */ + default: + return UA_STATUSCODE_BADINTERNALERROR; + } + + return UA_STATUSCODE_GOOD; +} + +START_TEST(CustomPublisher) { + /* Prepare the values */ + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + valueStore[i] = (UA_UInt32) i + 1; + UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]); + dvStore[i].hasValue = true; + dvPointers[i] = &dvStore[i]; + } + + /* Initialize the timer */ + struct sigevent sigev; + memset(&sigev, 0, sizeof(sigev)); + sigev.sigev_notify = SIGEV_THREAD; + sigev.sigev_notify_function = writerGroupPublishTrigger; + timer_create(CLOCK_REALTIME, &sigev, &writerGroupTimer); + + /* Add a PubSubConnection */ + UA_PubSubConnectionConfig connectionConfig; + memset(&connectionConfig, 0, sizeof(connectionConfig)); + connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); + connectionConfig.transportProfileUri = + UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + UA_NetworkAddressUrlDataType networkAddressUrl = + {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + connectionConfig.publisherId.id.uint16 = 2234; + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); + + /* Add a PublishedDataSet */ + UA_PublishedDataSetConfig publishedDataSetConfig; + memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); + publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; + publishedDataSetConfig.name = UA_STRING("Demo PDS"); + UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); + + /* Add DataSetFields with static value source to PDS */ + UA_DataSetFieldConfig dsfConfig; + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + /* TODO: Point to a variable in the information model */ + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; + dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; + UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); + } + + /* Add a WriterGroup */ + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + writerGroupConfig.customStateMachine = writerGroupStateMachine; + + /* Change message settings of writerGroup to send PublisherId, WriterGroupId + * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ + UA_UadpWriterGroupMessageDataType writerGroupMessage; + UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); + writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); + + UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent); + + /* Add a DataSetWriter to the WriterGroup */ + UA_NodeId dataSetWriterIdent; + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + dataSetWriterConfig.keyFrameCount = 10; + dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType; + UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType); + uadpDataSetWriterMessageDataType.dataSetMessageContentMask = + UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings, + &uadpDataSetWriterMessageDataType, + &UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]); + + UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, + &dataSetWriterConfig, &dataSetWriterIdent); + + UA_Server_enableAllPubSubComponents(server); + + /* Add a callback that updates the value */ + UA_UInt64 callbackId; + UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL, + PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId); + + UA_fakeSleep(PUBSUB_CONFIG_PUBLISH_CYCLE_MS); + UA_Server_run_iterate(server, true); + UA_fakeSleep(PUBSUB_CONFIG_PUBLISH_CYCLE_MS); + UA_Server_run_iterate(server, true); + + /* Cleanup */ + UA_Server_run_shutdown(server); + UA_StatusCode rv = UA_Server_delete(server); + ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); + timer_delete(writerGroupTimer); +} END_TEST + +static void * +listenUDP(void *_) { + (void)_; + + /* Block SIGINT for correct shutdown via the main thread */ + sigset_t blockset; + sigemptyset(&blockset); + sigaddset(&blockset, SIGINT); + sigprocmask(SIG_BLOCK, &blockset, NULL); + + /* Extract the hostname */ + UA_UInt16 port = 0; + UA_String hostname = UA_STRING_NULL; + UA_parseEndpointUrl(&networkAddressUrl.url, &hostname, &port, NULL); + + /* Get all the interface and IPv4/6 combinations for the configured hostname */ + struct addrinfo hints, *info; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 and IPv6 */ + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE; + + /* getaddrinfo */ + char portstr[6]; + char hostnamebuf[256]; + snprintf(portstr, 6, "%d", port); + memcpy(hostnamebuf, hostname.data, hostname.length); + hostnamebuf[hostname.length] = 0; + int result = getaddrinfo(hostnamebuf, portstr, &hints, &info); + if(result != 0) { + printf("XXX getaddrinfo failed\n"); + return NULL; + } + + /* Open the socket */ + listenSocket = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + if(listenSocket <= 0) { + printf("XXX Cannot create the socket\n"); + return NULL; + } + + /* Set socket options */ + int opts = fcntl(listenSocket, F_GETFL); + result |= fcntl(listenSocket, F_SETFL, opts | O_NONBLOCK); + int optval = 1; + result |= setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, + (const char*)&optval, sizeof(optval)); + if(result < 0) { + printf("XXX Cannot set the socket options\n"); + return NULL; + } + + /* Bind the socket */ + result = bind(listenSocket, info->ai_addr, (socklen_t)info->ai_addrlen); + if(result < 0) { + printf("XXX Cannot bind the socket\n"); + return NULL; + } + + /* Join the multicast group */ + if(info->ai_family == AF_INET) { + struct ip_mreqn ipv4; + struct sockaddr_in *sin = (struct sockaddr_in *)info->ai_addr; + ipv4.imr_multiaddr = sin->sin_addr; + ipv4.imr_address.s_addr = htonl(INADDR_ANY); /* default ANY */ + ipv4.imr_ifindex = 0; + result = setsockopt(listenSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipv4, sizeof(ipv4)); + } else if(info->ai_family == AF_INET6) { + struct ipv6_mreq ipv6; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)info->ai_addr; + ipv6.ipv6mr_multiaddr = sin6->sin6_addr; + ipv6.ipv6mr_interface = 0; /* default ANY interface */ + result = setsockopt(listenSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6, sizeof(ipv6)); + } + if(result < 0) { + printf("XXX Cannot join the multicast group\n"); + return NULL; + } + + freeaddrinfo(info); + + /* The connection is open, change the state to OPERATIONAL. + * The state machine checks whether listenSocket != 0. */ + printf("XXX Listening on UDP multicast (%s, port %u)\n", + hostnamebuf, (unsigned)port); + UA_Server_enablePubSubConnection(server, connectionIdentifier); + + /* Poll and process in a loop. + * The socket is closed in the state machine and */ + struct pollfd pfd; + pfd.fd = listenSocket; + pfd.events = POLLIN; + while(true) { + result = poll(&pfd, 1, -1); /* infinite timeout */ + if(pfd.revents & POLLERR || pfd.revents & POLLHUP || pfd.revents & POLLNVAL) + break; + + if(pfd.revents & POLLIN) { + static char buf[1024]; + ssize_t size = read(listenSocket, buf, sizeof(buf)); + if(size > 0) { + printf("XXX Received a packet\n"); + UA_ByteString packet = {(size_t)size, (UA_Byte*)buf}; + UA_Server_processPubSubConnectionReceive(server, connectionIdentifier, packet); + } + } + } + + printf("XXX The UDP multicast connection is closed\n"); + + /* Clean up and notify the state machine */ + close(listenSocket); + listenSocket = 0; + UA_Server_disablePubSubConnection(server, connectionIdentifier); + return NULL; +} + +static UA_StatusCode +connectionStateMachine(UA_Server *server, const UA_NodeId componentId, + void *componentContext, UA_PubSubState *state, + UA_PubSubState targetState) { + if(targetState == *state) + return UA_STATUSCODE_GOOD; + + switch(targetState) { + /* Disabled or Error */ + case UA_PUBSUBSTATE_ERROR: + case UA_PUBSUBSTATE_DISABLED: + case UA_PUBSUBSTATE_PAUSED: + printf("XXX Closing the UDP multicast connection\n"); + if(listenSocket != 0) + shutdown(listenSocket, SHUT_RDWR); + *state = targetState; + break; + + /* Operational */ + case UA_PUBSUBSTATE_PREOPERATIONAL: + case UA_PUBSUBSTATE_OPERATIONAL: + if(listenSocket != 0) { + *state = UA_PUBSUBSTATE_OPERATIONAL; + break; + } + printf("XXX Opening the UDP multicast connection\n"); + *state = UA_PUBSUBSTATE_PREOPERATIONAL; + int res = pthread_create(&listenThread, NULL, listenUDP, NULL); + if(res != 0) + return UA_STATUSCODE_BADINTERNALERROR; + break; + + /* Unknown state */ + default: + return UA_STATUSCODE_BADINTERNALERROR; + } + + return UA_STATUSCODE_GOOD; +} + +/* Simulate a custom data sink (e.g. shared memory) */ +UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; + +/* If the external data source is written over the information model, the + * externalDataWriteCallback will be triggered. The user has to take care and assure + * that the write leads not to synchronization issues and race conditions. */ +static UA_StatusCode +externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeId, + void *nodeContext, const UA_NumericRange *range, + const UA_DataValue *data){ + //node values are updated by using variables in the memory + //UA_Server_write is not used for updating node values. + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, + void *sessionContext, const UA_NodeId *nodeid, + void *nodeContext, const UA_NumericRange *range){ + //allow read without any preparation + return UA_STATUSCODE_GOOD; +} + +static void +subscribeAfterWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId, + const UA_NodeId *readerGroupId, + const UA_NodeId *targetVariableId, + void *targetVariableContext, + UA_DataValue **externalDataValue) { + (void) server; + (void) dataSetReaderId; + (void) readerGroupId; + (void) targetVariableContext; + + ck_assert(targetVariableId != 0); + ck_assert(externalDataValue != 0); +} + +/* Callback gets triggered before subscriber has received data received data + * hasn't been copied/handled yet */ +static void +subscribeBeforeWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId, + const UA_NodeId *readerGroupId, const UA_NodeId *targetVariableId, + void *targetVariableContext, UA_DataValue **externalDataValue) { + (void) server; + (void) dataSetReaderId; + (void) readerGroupId; + (void) targetVariableContext; + + ck_assert(targetVariableId != 0); + ck_assert(externalDataValue != 0); +} + +/* Define MetaData for TargetVariables */ +static void +fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { + if(pMetaData == NULL) + return; + + UA_DataSetMetaDataType_init (pMetaData); + pMetaData->name = UA_STRING ("DataSet 1"); + + /* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT + * to create targetVariables */ + pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + + for(size_t i = 0; i < pMetaData->fieldsSize; i++) { + /* UInt32 DataType */ + UA_FieldMetaData_init (&pMetaData->fields[i]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[i].dataType); + pMetaData->fields[i].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[i].name = UA_STRING ("UInt32 varibale"); + pMetaData->fields[i].valueRank = -1; /* scalar */ + } +} + +/* Add new connection to the server */ +static void +addPubSubConnection(UA_Server *server) { + /* Configuration creation for the connection */ + UA_PubSubConnectionConfig connectionConfig; + memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); + connectionConfig.name = UA_STRING("UDPMC Connection 1"); + connectionConfig.transportProfileUri = transportProfile; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; + connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); + connectionConfig.customStateMachine = connectionStateMachine; + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); +} + +/* Add ReaderGroup to the created connection */ +static void +addReaderGroup(UA_Server *server) { + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING("ReaderGroup1"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; + UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); +} + +/* Set SubscribedDataSet type to TargetVariables data type + * Add subscribedvariables to the DataSetReader */ +static void +addSubscribedVariables (UA_Server *server) { + UA_NodeId folderId; + UA_NodeId newnodeId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if(folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER), + UA_NS0ID(ORGANIZES), folderBrowseName, + UA_NS0ID(BASEOBJECTTYPE), oAttr, + NULL, &folderId); + + /* Set the subscribed data to TargetVariable type */ + readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; + /* Create the TargetVariables with respect to DataSetMetaData fields */ + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = + readerConfig.dataSetMetaData.fieldsSize; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = + (UA_FieldTargetVariable *)UA_calloc( + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, + sizeof(UA_FieldTargetVariable)); + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + /* Variable to subscribe data */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + // Initialize the values at first to create the buffered NetworkMessage + // with correct size and offsets + UA_Variant value; + UA_Variant_init(&value); + UA_UInt32 intValue = 0; + UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]); + vAttr.value = value; + UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), + folderId, UA_NS0ID(HASCOMPONENT), + UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NS0ID(BASEDATAVARIABLETYPE), + vAttr, NULL, &newnodeId); + repeatedFieldValues[i] = 0; + repeatedDataValueRT[i] = UA_DataValue_new(); + UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], + &UA_TYPES[UA_TYPES_UINT32]); + repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; + repeatedDataValueRT[i]->hasValue = true; + + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &repeatedDataValueRT[i]; + valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; + valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; + UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); + + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + + /* For creating Targetvariables */ + UA_FieldTargetDataType_init(ftdt); + ftdt->attributeId = UA_ATTRIBUTEID_VALUE; + ftdt->targetNodeId = newnodeId; + /* set both before and after write callback to show the usage */ + tv->beforeWrite = subscribeBeforeWriteCallback; + tv->externalDataValue = &repeatedDataValueRT[i]; + tv->afterWrite = subscribeAfterWriteCallback; + } +} + +/* Add DataSetReader to the ReaderGroup */ +static void +addDataSetReader(UA_Server *server) { + memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING("DataSet Reader 1"); + /* Parameters to filter which DataSetMessage has to be processed + * by the DataSetReader */ + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + readerConfig.publisherId.id.uint16 = publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW; + readerConfig.messageSettings.content.decoded.type = + &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = + UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | + UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + + readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + /* Setting up Meta data configuration in DataSetReader */ + fillTestDataSetMetaData(&readerConfig.dataSetMetaData); + + addSubscribedVariables(server); + UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); + + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + UA_FieldTargetDataType_clear(ftdt); + } + + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); +} + +START_TEST(CustomSubscriber) { + addPubSubConnection(server); + addReaderGroup(server); + addDataSetReader(server); + UA_Server_enableAllPubSubComponents(server); + + UA_fakeSleep(PUBSUB_CONFIG_PUBLISH_CYCLE_MS); + UA_Server_run_iterate(server, true); + UA_fakeSleep(PUBSUB_CONFIG_PUBLISH_CYCLE_MS); + UA_Server_run_iterate(server, true); + + UA_Server_run_shutdown(server); + + pthread_join(listenThread, NULL); + + UA_StatusCode rv = UA_Server_delete(server); + ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); + for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + UA_DataValue_delete(repeatedDataValueRT[i]); + } +} END_TEST + +int main(void) { + TCase *tc_custom = tcase_create("Custom State Machine"); + tcase_add_checked_fixture(tc_custom, setup, NULL); + tcase_add_test(tc_custom, CustomPublisher); + tcase_add_test(tc_custom, CustomSubscriber); + + Suite *s = suite_create("PubSub Custom State Machine"); + suite_add_tcase(s, tc_custom); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr,CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} From bce1e4619c938c5eb67eb54726b7f40748d691d7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 11:31:11 +0100 Subject: [PATCH 072/158] refactor(examples): Align PubSub-RT example and source file name --- examples/CMakeLists.txt | 4 ++-- ...erver_pubsub_publisher_rt.c => server_pubsub_publish_rt.c} | 0 ...er_pubsub_subscriber_rt.c => server_pubsub_subscribe_rt.c} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename examples/pubsub_realtime/{server_pubsub_publisher_rt.c => server_pubsub_publish_rt.c} (100%) rename examples/pubsub_realtime/{server_pubsub_subscriber_rt.c => server_pubsub_subscribe_rt.c} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3b4a58b24..5da175b56 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -212,9 +212,9 @@ if(UA_ENABLE_PUBSUB) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) if(UA_ARCHITECTURE_POSIX) - add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publisher_rt.c) + add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publish_rt.c) target_link_libraries(server_pubsub_publish_rt "rt") - add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscriber_rt.c) + add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscribe_rt.c) target_link_libraries(server_pubsub_subscribe_rt "rt") endif() if (BUILD_SHARED_LIBS) diff --git a/examples/pubsub_realtime/server_pubsub_publisher_rt.c b/examples/pubsub_realtime/server_pubsub_publish_rt.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_publisher_rt.c rename to examples/pubsub_realtime/server_pubsub_publish_rt.c diff --git a/examples/pubsub_realtime/server_pubsub_subscriber_rt.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_subscriber_rt.c rename to examples/pubsub_realtime/server_pubsub_subscribe_rt.c From b4e9d81436d14fce5f9e23123e998a33c01f1cda Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 11:33:18 +0100 Subject: [PATCH 073/158] refactor(examples): Always build pubsub_subscribe_standalone_dataset It no longer depends on internal APIs --- examples/CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5da175b56..a499a0e7a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -211,17 +211,13 @@ if(UA_ENABLE_PUBSUB) add_example(tutorial_pubsub_subscribe pubsub/tutorial_pubsub_subscribe.c) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) + add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) if(UA_ARCHITECTURE_POSIX) add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publish_rt.c) target_link_libraries(server_pubsub_publish_rt "rt") add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscribe_rt.c) target_link_libraries(server_pubsub_subscribe_rt "rt") endif() - if (BUILD_SHARED_LIBS) - message(WARNING "Build option BUILD_SHARED_LIBS not supported for standalone subscriber and realtime examples. Skipping these examples.") - else (NOT BUILD_SHARED_LIBS) - add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) - endif() if(UA_ENABLE_ENCRYPTION_MBEDTLS) add_example(pubsub_publish_encrypted pubsub/pubsub_publish_encrypted.c) add_example(pubsub_subscribe_encrypted pubsub/pubsub_subscribe_encrypted.c) From 51ad1d57479b91ee8bc6a1486b01679794b64a0b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 11:35:27 +0100 Subject: [PATCH 074/158] refactor(examples): Always enable the custom datatype examples --- examples/CMakeLists.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a499a0e7a..1442ecc44 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -155,6 +155,10 @@ add_example(server_inheritance server_inheritance.c) add_example(server_loglevel server_loglevel.c) +add_example(custom_datatype_client custom_datatype/client_types_custom.c) + +add_example(custom_datatype_server custom_datatype/server_types_custom.c) + if(UA_ENABLE_JSON_ENCODING) add_example(server_json_config server_json_config.c) endif() @@ -177,14 +181,6 @@ if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ endif() endif() -if (NOT (BUILD_SHARED_LIBS AND WIN32)) - add_example(custom_datatype_client custom_datatype/client_types_custom.c) - add_example(custom_datatype_server custom_datatype/server_types_custom.c) -else() - MESSAGE(WARNING "Can't build custom datatype examples on WIN32 when BUILD_SHARED_LIBS enabled. Skipping -custom_datatype_client and custom_datatype_server!") -endif() - if(UA_ENABLE_NODEMANAGEMENT) add_example(access_control_server access_control/server_access_control.c) add_example(access_control_client access_control/client_access_control.c) @@ -201,6 +197,7 @@ if(UA_ENABLE_DISCOVERY_MULTICAST) endif() add_subdirectory(nodeset) + #################### # Example PubSub # #################### @@ -255,6 +252,7 @@ endif() ########################### # Nodeser Loader Examples # ########################### + if(UA_ENABLE_NODESETLOADER) add_subdirectory(nodeset_loader) endif() From 63057d83d8d754aaac5bac8edf9a955b5f04804c Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 11:37:57 +0100 Subject: [PATCH 075/158] refactor(examples): server_pubsub_file_configuration does not use the internal API --- examples/CMakeLists.txt | 3 --- examples/pubsub/server_pubsub_file_configuration.c | 14 ++++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1442ecc44..0570aac2c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -239,10 +239,7 @@ if(UA_ENABLE_PUBSUB) endif() if(UA_ENABLE_PUBSUB_FILE_CONFIG) - # TODO: This example accesses a private API add_example(server_pubsub_file_configuration pubsub/server_pubsub_file_configuration.c) - target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../src/pubsub") - target_include_directories(server_pubsub_file_configuration PUBLIC "${PROJECT_SOURCE_DIR}/../deps") endif() add_example(server_pubsub_subscribe_custom_monitoring diff --git a/examples/pubsub/server_pubsub_file_configuration.c b/examples/pubsub/server_pubsub_file_configuration.c index 4e75a3515..564097ede 100644 --- a/examples/pubsub/server_pubsub_file_configuration.c +++ b/examples/pubsub/server_pubsub_file_configuration.c @@ -6,14 +6,14 @@ #include #include -#include "ua_pubsub_internal.h" - #include "common.h" /* Function to give user information about correct usage */ static void usage_info(void) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Alternatively, Bin-files can be loaded via configuration method calls."); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "USAGE: ./server_pubsub_file_configuration [name of UA_Binary_Config_File]"); + UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, + "Alternatively, Bin-files can be loaded via configuration method calls."); } int main(int argc, char** argv) { @@ -107,7 +107,8 @@ int main(int argc, char** argv) { statusCode |= UA_Server_enableAllPubSubComponents(server); statusCode |= UA_Server_runUntilInterrupt(server); if(statusCode != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Server stopped. Status code: 0x%x\n", statusCode); + UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Server stopped. Status code: 0x%x\n", statusCode); return(-1); } @@ -120,7 +121,8 @@ int main(int argc, char** argv) { if(statusCode != UA_STATUSCODE_GOOD) UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Saving PubSub configuration to file failed. StatusCode: 0x%x\n", statusCode); + "Saving PubSub configuration to file failed. " + "StatusCode: 0x%x\n", statusCode); UA_ByteString_clear(&buffer); } From 667e2f09db7ab354246282c0e4021112f6ee1914 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 11:40:47 +0100 Subject: [PATCH 076/158] refactor(examples): Cleanup of /examples/CMakeLists.txt --- examples/CMakeLists.txt | 128 +++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 74 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0570aac2c..ad0af2f71 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,45 +1,34 @@ -cmake_minimum_required(VERSION 3.0...3.12) +cmake_minimum_required(VERSION 3.13) project(open62541-examples C) if(${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() -# This examples folder can also be built standalone. -# First install open62541 using `make install` then -# copy this folder to any other location and call CMake directly: + +# The examples folder can be built standalone. +# First install open62541 system-wide using `make install`. +# Then copy this folder to any other location and call CMake directly: # -# cp -r open62541/examples $HOME/open62541_examples -# cd $HOME/open62541_examples +# cd ./open62541_examples # mkdir build && cd build -# cmake -DUA_NAMESPACE_ZERO=FULL .. +# cmake .. # make -j if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) # Examples are built standalone. Find installed open62541 + find_package(open62541 REQUIRED) - if(UA_NAMESPACE_ZERO STREQUAL "FULL") - find_package(open62541 REQUIRED COMPONENTS FullNamespace) - else() - find_package(open62541 REQUIRED) - endif() - - if(NOT UA_TOOLS_DIR) - set(UA_TOOLS_DIR ${open62541_TOOLS_DIR}) - endif() - + # Define empty function. We don't need it in standalone function(assign_source_group) - # define empty function. We don't need it in standalone endfunction(assign_source_group) - - include_directories(${PROJECT_BINARY_DIR}/src_generated) endif() +##################### +# CMake Definitions # +##################### + # Required for common.h header file used in examples include_directories(${CMAKE_CURRENT_LIST_DIR}) -############################# -# Compiled binaries folders # -############################# - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/examples) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/examples) @@ -47,19 +36,21 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/exampl set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/examples) macro(add_example EXAMPLE_NAME EXAMPLE_SOURCE) - add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS} ${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h) + add_executable(${EXAMPLE_NAME} ${STATIC_OBJECTS} + ${EXAMPLE_SOURCE} ${ARGN} ${PROJECT_SOURCE_DIR}/common.h) target_link_libraries(${EXAMPLE_NAME} open62541::open62541) assign_source_group(${EXAMPLE_SOURCE}) set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER "open62541/examples") - set_target_properties(${EXAMPLE_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + set_target_properties(${EXAMPLE_NAME} PROPERTIES + VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin") if(UA_ENABLE_NODESET_INJECTOR) set(UA_NODESETINJECTOR_EXAMPLE_NAMES ${EXAMPLE_NAME} ${UA_NODESETINJECTOR_EXAMPLE_NAMES}) # If the nodeset injector is activated, the target must be built twice. # Otherwise, it may result in the nodesets not being inserted. add_custom_command(TARGET ${EXAMPLE_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${EXAMPLE_NAME} - ) + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --target ${EXAMPLE_NAME}) endif() endmacro() @@ -68,25 +59,17 @@ endmacro() ############# add_example(tutorial_datatypes tutorial_datatypes.c) - add_example(tutorial_server_firststeps tutorial_server_firststeps.c) - add_example(tutorial_server_variable tutorial_server_variable.c) - add_example(tutorial_server_datasource tutorial_server_datasource.c) - -add_example(server_settimestamp server_settimestamp.c) +add_example(tutorial_server_variabletype tutorial_server_variabletype.c) +add_example(tutorial_server_object tutorial_server_object.c) +add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c) if(UA_ENABLE_SUBSCRIPTIONS) add_example(tutorial_server_monitoreditems tutorial_server_monitoreditems.c) endif() -add_example(tutorial_server_variabletype tutorial_server_variabletype.c) - -add_example(tutorial_server_object tutorial_server_object.c) - -add_example(tutorial_server_reverseconnect tutorial_server_reverseconnect.c) - if(UA_ENABLE_METHODCALLS) add_example(tutorial_server_method tutorial_server_method.c) if (UA_MULTITHREADING GREATER_EQUAL 100) @@ -94,19 +77,15 @@ if(UA_ENABLE_METHODCALLS) endif() endif() +if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) + add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c) +endif() + add_example(tutorial_client_firststeps tutorial_client_firststeps.c) if(UA_ENABLE_SUBSCRIPTIONS_EVENTS) - add_example(tutorial_client_events tutorial_client_events.c) - add_example(tutorial_server_events tutorial_server_events.c) - add_example(server_events_random events/server_random_events.c) - add_example(client_event_filter events/client_eventfilter.c) - if(UA_ENABLE_PARSING) - add_example(client_event_filter_queries events/client_filter_queries.c) - endif() - if(UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS) - add_example(tutorial_server_alarms_conditions tutorial_server_alarms_conditions.c) - endif() + add_example(tutorial_client_events tutorial_client_events.c) + add_example(tutorial_server_events tutorial_server_events.c) endif() ################## @@ -114,27 +93,18 @@ endif() ################## add_example(client client.c) - add_example(client_connect client_connect.c) +add_example(client_async client_async.c) +add_example(client_connect_loop client_connect_loop.c) if(UA_ENABLE_HISTORIZING) add_example(client_historical client_historical.c) endif() -install(PROGRAMS $ - DESTINATION bin - RENAME ua_client) - -add_example(client_async client_async.c) - -if(UA_ENABLE_METHODCALLS) - if (UA_MULTITHREADING GREATER_EQUAL 100) - add_example(client_method_async client_method_async.c) - endif() +if(UA_ENABLE_METHODCALLS AND UA_MULTITHREADING GREATER_EQUAL 100) + add_example(client_method_async client_method_async.c) endif() -add_example(client_connect_loop client_connect_loop.c) - if(UA_ENABLE_SUBSCRIPTIONS) add_example(client_subscription_loop client_subscription_loop.c) endif() @@ -144,21 +114,23 @@ endif() #################### add_example(ci_server ci_server.c) - +add_example(server_settimestamp server_settimestamp.c) add_example(server_mainloop server_mainloop.c) - add_example(server_instantiation server_instantiation.c) - add_example(server_repeated_job server_repeated_job.c) - add_example(server_inheritance server_inheritance.c) - add_example(server_loglevel server_loglevel.c) - add_example(custom_datatype_client custom_datatype/client_types_custom.c) - add_example(custom_datatype_server custom_datatype/server_types_custom.c) +if(UA_ENABLE_SUBSCRIPTIONS_EVENTS) + add_example(server_events_random events/server_random_events.c) + add_example(client_event_filter events/client_eventfilter.c) + if(UA_ENABLE_PARSING) + add_example(client_event_filter_queries events/client_filter_queries.c) + endif() +endif() + if(UA_ENABLE_JSON_ENCODING) add_example(server_json_config server_json_config.c) endif() @@ -168,7 +140,9 @@ if(UA_ENABLE_HISTORIZING) add_example(tutorial_server_historicaldata_circular tutorial_server_historicaldata_circular.c) endif() -if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") +if(UA_ENABLE_ENCRYPTION OR + UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR + UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") add_example(server_encryption encryption/server_encryption.c) add_example(client_encryption encryption/client_encryption.c) target_include_directories(server_encryption PRIVATE "${PROJECT_SOURCE_DIR}/examples") @@ -184,8 +158,10 @@ endif() if(UA_ENABLE_NODEMANAGEMENT) add_example(access_control_server access_control/server_access_control.c) add_example(access_control_client access_control/client_access_control.c) - if(UA_ENABLE_ENCRYPTION OR UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") - add_example(access_control_client_encrypt access_control/client_access_control_encrypt.c) + if(UA_ENABLE_ENCRYPTION OR + UA_ENABLE_ENCRYPTION STREQUAL "MBEDTLS" OR + UA_ENABLE_ENCRYPTION STREQUAL "OPENSSL") + add_example(access_control_client_encrypt access_control/client_access_control_encrypt.c) endif() endif() @@ -209,12 +185,14 @@ if(UA_ENABLE_PUBSUB) add_example(server_pubsub_publish_on_demand pubsub/server_pubsub_publisher_on_demand.c) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) + if(UA_ARCHITECTURE_POSIX) add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publish_rt.c) target_link_libraries(server_pubsub_publish_rt "rt") add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscribe_rt.c) target_link_libraries(server_pubsub_subscribe_rt "rt") endif() + if(UA_ENABLE_ENCRYPTION_MBEDTLS) add_example(pubsub_publish_encrypted pubsub/pubsub_publish_encrypted.c) add_example(pubsub_subscribe_encrypted pubsub/pubsub_subscribe_encrypted.c) @@ -223,8 +201,10 @@ if(UA_ENABLE_PUBSUB) add_example(pubsub_subscribe_encrypted_tpm pubsub/pubsub_subscribe_encrypted_tpm.c) endif() if(UA_ENABLE_TPM2_KEYSTORE) - add_example(pubsub_publish_encrypted_tpm_keystore pubsub/pubsub_publish_encrypted_tpm_keystore.c) - add_example(pubsub_subscribe_encrypted_tpm_keystore pubsub/pubsub_subscribe_encrypted_tpm_keystore.c) + add_example(pubsub_publish_encrypted_tpm_keystore + pubsub/pubsub_publish_encrypted_tpm_keystore.c) + add_example(pubsub_subscribe_encrypted_tpm_keystore + pubsub/pubsub_subscribe_encrypted_tpm_keystore.c) target_link_libraries(pubsub_publish_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto) target_link_libraries(pubsub_subscribe_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto) endif() From c6e4d5e480b1f42417ec122f385d3187a30f6cc6 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 20:07:51 +0100 Subject: [PATCH 077/158] fix(examples): Fix building the custom_dataype examples on MSVC with a .dll shared library --- .../custom_datatype/client_types_custom.c | 2 + examples/custom_datatype/custom_datatype.h | 290 ++++++++++-------- .../custom_datatype/server_types_custom.c | 2 + 3 files changed, 158 insertions(+), 136 deletions(-) diff --git a/examples/custom_datatype/client_types_custom.c b/examples/custom_datatype/client_types_custom.c index a8a3200d1..536244021 100644 --- a/examples/custom_datatype/client_types_custom.c +++ b/examples/custom_datatype/client_types_custom.c @@ -12,6 +12,8 @@ #define STRING_BUFFER_SIZE 20 int main(void) { + setupCustomTypes(); + /* Make your custom datatype known to the stack */ UA_DataType types[4]; types[0] = PointType; diff --git a/examples/custom_datatype/custom_datatype.h b/examples/custom_datatype/custom_datatype.h index e759f86e3..6461b2255 100644 --- a/examples/custom_datatype/custom_datatype.h +++ b/examples/custom_datatype/custom_datatype.h @@ -1,6 +1,10 @@ /* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ +/* For dynamic linking (with .dll) on Windows, the memory locations from the dll + * are not available during compilation. This prevents the use of constant initializers. + * So the datatype definitions need to be set up in a method call at runtime. */ + typedef struct { UA_Float x; UA_Float y; @@ -17,48 +21,8 @@ typedef struct { #define Opt_binary_encoding_id 3 #define Uni_binary_encoding_id 4 - -static UA_DataTypeMember Point_members[3] = { - /* x */ - { - UA_TYPENAME("x") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - 0, /* .padding */ - false, /* .isArray */ - false /* .isOptional */ - }, - /* y */ - { - UA_TYPENAME("y") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - Point_padding_y, /* .padding */ - false, /* .isArray */ - false /* .isOptional */ - }, - /* z */ - { - UA_TYPENAME("z") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - Point_padding_z, /* .padding */ - false, /* .isArray */ - false /* .isOptional */ - } -}; - -static const UA_DataType PointType = { - UA_TYPENAME("Point") /* .tyspeName */ - {1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */ - {1, UA_NODEIDTYPE_NUMERIC, {Point_binary_encoding_id}}, /* .binaryEncodingId, the numeric - identifier used on the wire (the - namespaceindex is from .typeId) */ - sizeof(Point), /* .memSize */ - UA_DATATYPEKIND_STRUCTURE, /* .typeKind */ - true, /* .pointerFree */ - false, /* .overlayable (depends on endianness and - the absence of padding) */ - 3, /* .membersSize */ - Point_members -}; +static UA_DataTypeMember Point_members[3]; +static UA_DataType PointType; /* The datatype description for the Measurement-Series datatype (Array Example)*/ typedef struct { @@ -67,38 +31,8 @@ typedef struct { UA_Float *measurement; } Measurements; -static UA_DataTypeMember Measurements_members[2] = { - { - UA_TYPENAME("Measurement description") /* .memberName */ - &UA_TYPES[UA_TYPES_STRING], /* .memberType */ - 0, /* .padding */ - false, /* .isArray */ - false /* .isOptional */ - }, - { - UA_TYPENAME("Measurements") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - 0, /* .padding */ - true, /* .isArray */ - false /* .isOptional */ - } -}; - -static const UA_DataType MeasurementType = { - UA_TYPENAME("Measurement") /* .typeName */ - {1, UA_NODEIDTYPE_NUMERIC, {4443}}, /* .typeId */ - {1, UA_NODEIDTYPE_NUMERIC, {Measurement_binary_encoding_id}}, /* .binaryEncodingId, the numeric - identifier used on the wire (the - namespaceindex is from .typeId) */ - sizeof(Measurements), /* .memSize */ - UA_DATATYPEKIND_STRUCTURE, /* .typeKind */ - false, /* .pointerFree */ - false, /* .overlayable (depends on endianness and - the absence of padding) */ - 2, /* .membersSize */ - Measurements_members -}; - +static UA_DataTypeMember Measurements_members[2]; +static UA_DataType MeasurementType; /* The datatype description for the Opt datatype (Structure with optional fields example)*/ typedef struct { @@ -107,50 +41,15 @@ typedef struct { UA_Float *c; } Opt; -static UA_DataTypeMember Opt_members[3] = { - /* a */ - { - UA_TYPENAME("a") /* .memberName */ - &UA_TYPES[UA_TYPES_INT16], /* .memberType */ - 0, /* .padding */ - false, /* .isArray */ - false /* .isOptional */ - }, - /* b */ - { - UA_TYPENAME("b") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - offsetof(Opt,b) - offsetof(Opt,a) - sizeof(UA_Int16), /* .padding */ - false, /* .isArray */ - true /* .isOptional */ - }, - /* c */ - { - UA_TYPENAME("c") /* .memberName */ - &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ - offsetof(Opt,c) - offsetof(Opt,b) - sizeof(void *), /* .padding */ - false, /* .isArray */ - true /* .isOptional */ - } -}; - -static const UA_DataType OptType = { - UA_TYPENAME("Opt") /* .typeName */ - {1, UA_NODEIDTYPE_NUMERIC, {4644}}, /* .typeId */ - {1, UA_NODEIDTYPE_NUMERIC, {Opt_binary_encoding_id}}, /* .binaryEncodingId, the numeric - identifier used on the wire (the - namespaceindex is from .typeId) */ - sizeof(Opt), /* .memSize */ - UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */ - false, /* .pointerFree */ - false, /* .overlayable (depends on endianness and - the absence of padding) */ - 3, /* .membersSize */ - Opt_members -}; +static UA_DataTypeMember Opt_members[3]; +static UA_DataType OptType; /* The datatype description for the Uni datatype (Union example) */ -typedef enum {UA_UNISWITCH_NONE = 0, UA_UNISWITCH_OPTIONA = 1, UA_UNISWITCH_OPTIONB = 2} UA_UniSwitch; +typedef enum { + UA_UNISWITCH_NONE = 0, + UA_UNISWITCH_OPTIONA = 1, + UA_UNISWITCH_OPTIONB = 2 +} UA_UniSwitch; typedef struct { UA_UniSwitch switchField; @@ -160,34 +59,153 @@ typedef struct { } fields; } Uni; -static UA_DataTypeMember Uni_members[2] = { - { +static UA_DataTypeMember Uni_members[2]; +static UA_DataType UniType; + +static void setupCustomTypes(void) { + /* x */ + Point_members[0] = (UA_DataTypeMember){ + UA_TYPENAME("x") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + 0, /* .padding */ + false, /* .isArray */ + false /* .isOptional */ + }; + + /* y */ + Point_members[1] = (UA_DataTypeMember){ + UA_TYPENAME("y") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + Point_padding_y, /* .padding */ + false, /* .isArray */ + false /* .isOptional */ + }; + + /* z */ + Point_members[2] = (UA_DataTypeMember){ + UA_TYPENAME("z") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + Point_padding_z, /* .padding */ + false, /* .isArray */ + false /* .isOptional */ + }; + + PointType = (UA_DataType){ + UA_TYPENAME("Point") /* .typeName */ + {1, UA_NODEIDTYPE_NUMERIC, { 4242 }}, /* .typeId */ + { 1, UA_NODEIDTYPE_NUMERIC, { Point_binary_encoding_id } }, /* .binaryEncodingId, the numeric + identifier used on the wire (the + namespaceindex is from .typeId) */ + sizeof(Point), /* .memSize */ + UA_DATATYPEKIND_STRUCTURE, /* .typeKind */ + true, /* .pointerFree */ + false, /* .overlayable (depends on endianness and + the absence of padding) */ + 3, /* .membersSize */ + Point_members + }; + + Measurements_members[0] = (UA_DataTypeMember) { + UA_TYPENAME("Measurement description") /* .memberName */ + &UA_TYPES[UA_TYPES_STRING], /* .memberType */ + 0, /* .padding */ + false, /* .isArray */ + false /* .isOptional */ + }; + + Measurements_members[1] = (UA_DataTypeMember) { + UA_TYPENAME("Measurements") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + 0, /* .padding */ + true, /* .isArray */ + false /* .isOptional */ + }; + + MeasurementType = (UA_DataType) { + UA_TYPENAME("Measurement") /* .typeName */ + {1, UA_NODEIDTYPE_NUMERIC, { 4443 }}, /* .typeId */ + { 1, UA_NODEIDTYPE_NUMERIC, { Measurement_binary_encoding_id } }, /* .binaryEncodingId, the numeric + identifier used on the wire (the + namespaceindex is from .typeId) */ + sizeof(Measurements), /* .memSize */ + UA_DATATYPEKIND_STRUCTURE, /* .typeKind */ + false, /* .pointerFree */ + false, /* .overlayable (depends on endianness and + the absence of padding) */ + 2, /* .membersSize */ + Measurements_members + }; + + /* a */ + Opt_members[0] = (UA_DataTypeMember) { + UA_TYPENAME("a") /* .memberName */ + &UA_TYPES[UA_TYPES_INT16], /* .memberType */ + 0, /* .padding */ + false, /* .isArray */ + false /* .isOptional */ + }; + + /* b */ + Opt_members[1] = (UA_DataTypeMember) { + UA_TYPENAME("b") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + offsetof(Opt, b) - offsetof(Opt, a) - sizeof(UA_Int16), /* .padding */ + false, /* .isArray */ + true /* .isOptional */ + }; + + /* c */ + Opt_members[2] = (UA_DataTypeMember) { + UA_TYPENAME("c") /* .memberName */ + &UA_TYPES[UA_TYPES_FLOAT], /* .memberType */ + offsetof(Opt, c) - offsetof(Opt, b) - sizeof(void *), /* .padding */ + false, /* .isArray */ + true /* .isOptional */ + }; + + OptType = (UA_DataType) { + UA_TYPENAME("Opt") /* .typeName */ + {1, UA_NODEIDTYPE_NUMERIC, { 4644 }}, /* .typeId */ + { 1, UA_NODEIDTYPE_NUMERIC, { Opt_binary_encoding_id } }, /* .binaryEncodingId, the numeric + identifier used on the wire (the + namespaceindex is from .typeId) */ + sizeof(Opt), /* .memSize */ + UA_DATATYPEKIND_OPTSTRUCT, /* .typeKind */ + false, /* .pointerFree */ + false, /* .overlayable (depends on endianness and + the absence of padding) */ + 3, /* .membersSize */ + Opt_members + }; + + Uni_members[0] = (UA_DataTypeMember) { UA_TYPENAME("optionA") /* .memberName */ &UA_TYPES[UA_TYPES_DOUBLE], /* .memberType */ offsetof(Uni, fields.optionA), /* .padding */ false, /* .isArray */ false /* .isOptional */ - }, - { + }; + + Uni_members[1] = (UA_DataTypeMember) { UA_TYPENAME("optionB") /* .memberName */ &UA_TYPES[UA_TYPES_STRING], /* .memberType */ offsetof(Uni, fields.optionB), /* .padding */ false, /* .isArray */ false /* .isOptional */ - } -}; + }; -static const UA_DataType UniType = { - UA_TYPENAME("Uni") /* .typeName */ - {1, UA_NODEIDTYPE_NUMERIC, {4845}}, /* .typeId */ - {1, UA_NODEIDTYPE_NUMERIC, {Uni_binary_encoding_id}}, /* .binaryEncodingId, the numeric - identifier used on the wire (the - namespaceindex is from .typeId) */ - sizeof(Uni), /* .memSize */ - UA_DATATYPEKIND_UNION, /* .typeKind */ - false, /* .pointerFree */ - false, /* .overlayable (depends on endianness and - the absence of padding) */ - 2, /* .membersSize */ - Uni_members -}; + UniType = (UA_DataType) { + UA_TYPENAME("Uni") /* .typeName */ + {1, UA_NODEIDTYPE_NUMERIC, { 4845 }}, /* .typeId */ + { 1, UA_NODEIDTYPE_NUMERIC, { Uni_binary_encoding_id } }, /* .binaryEncodingId, the numeric + identifier used on the wire (the + namespaceindex is from .typeId) */ + sizeof(Uni), /* .memSize */ + UA_DATATYPEKIND_UNION, /* .typeKind */ + false, /* .pointerFree */ + false, /* .overlayable (depends on endianness and + the absence of padding) */ + 2, /* .membersSize */ + Uni_members + }; +} diff --git a/examples/custom_datatype/server_types_custom.c b/examples/custom_datatype/server_types_custom.c index 6f98f44a5..99c592014 100644 --- a/examples/custom_datatype/server_types_custom.c +++ b/examples/custom_datatype/server_types_custom.c @@ -251,6 +251,8 @@ int main(void) { UA_ServerConfig *config = UA_Server_getConfig(server); UA_ServerConfig_setDefault(config); + setupCustomTypes(); + /* Make your custom datatype known to the stack */ UA_DataType *types = (UA_DataType*)UA_malloc(4 * sizeof(UA_DataType)); UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3); From b867ca3b55162409092dbbda9b2990bb7d0293db Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 23 Dec 2024 20:56:10 +0100 Subject: [PATCH 078/158] fix(examples): Fix build of custom_datatype.h for MSVC >2019 --- examples/custom_datatype/custom_datatype.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/custom_datatype/custom_datatype.h b/examples/custom_datatype/custom_datatype.h index 6461b2255..6b38953f3 100644 --- a/examples/custom_datatype/custom_datatype.h +++ b/examples/custom_datatype/custom_datatype.h @@ -181,7 +181,7 @@ static void setupCustomTypes(void) { Uni_members[0] = (UA_DataTypeMember) { UA_TYPENAME("optionA") /* .memberName */ &UA_TYPES[UA_TYPES_DOUBLE], /* .memberType */ - offsetof(Uni, fields.optionA), /* .padding */ + (UA_Byte)(offsetof(Uni, fields.optionA) - 0), /* .padding */ false, /* .isArray */ false /* .isOptional */ }; @@ -189,7 +189,7 @@ static void setupCustomTypes(void) { Uni_members[1] = (UA_DataTypeMember) { UA_TYPENAME("optionB") /* .memberName */ &UA_TYPES[UA_TYPES_STRING], /* .memberType */ - offsetof(Uni, fields.optionB), /* .padding */ + (UA_Byte)(offsetof(Uni, fields.optionB) - 0), /* .padding */ false, /* .isArray */ false /* .isOptional */ }; From 780229a4f43efb7ee3022af0e78ed17f846c0ddc Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Dec 2024 13:15:13 +0100 Subject: [PATCH 079/158] feat(ci): Use valgrind suppressions for the examples --- tools/ci/examples_with_valgrind.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/ci/examples_with_valgrind.py b/tools/ci/examples_with_valgrind.py index 941fa4b44..e994e8b55 100644 --- a/tools/ci/examples_with_valgrind.py +++ b/tools/ci/examples_with_valgrind.py @@ -109,6 +109,9 @@ print(f"Current directory: {current_dir}") example_dir = os.path.join(current_dir, "bin", "examples") examples = os.listdir(example_dir) +cur_dir = os.path.dirname(os.path.realpath(__file__)) # path of current file +tests_path = os.path.join(cur_dir, os.pardir, os.pardir, "tests") + # skipping examples that are in the blacklist for example in examples: if example in blacklist: @@ -117,7 +120,9 @@ for example in examples: # get the arguments for the example args = example_args.get(example) - cmd = ["valgrind", "--errors-for-leak-kinds=all", "--leak-check=full", "--error-exitcode=1337", "./bin/examples/"+example] + cmd = ["valgrind", "--errors-for-leak-kinds=all", "--leak-check=full", "--error-exitcode=1337", + f"--suppressions={tests_path}/valgrind_suppressions.supp", + "./bin/examples/"+example] if args: args_list = args.split() cmd += args_list From 6ce423916fe28be8dae531ebcc1a62149d896fb8 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 24 Dec 2024 13:17:34 +0100 Subject: [PATCH 080/158] feat(tests): Add a valgrind suppression for a singleton memory allocation in timer_create --- tests/valgrind_suppressions.supp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/valgrind_suppressions.supp b/tests/valgrind_suppressions.supp index 591ffa664..7309874be 100644 --- a/tests/valgrind_suppressions.supp +++ b/tests/valgrind_suppressions.supp @@ -9,7 +9,14 @@ fun:exit } - +{ + + Memcheck:Leak + match-leak-kinds: possible + ... + fun:timer_create@@* + ... +} # Custom suppressions added by @Pro From 5818691fc467476d45f2d17deb56c08bfa56d4a6 Mon Sep 17 00:00:00 2001 From: Federico Pellegrin Date: Mon, 30 Dec 2024 06:07:33 +0100 Subject: [PATCH 081/158] fix(deps): Remove maybe-uninitialized warning from ziptree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Warning appears (at least) with GCC 11.5.0 and 14.2.1 when using -O2, so reproduce for example with both -O2 and -Werror=maybe-uninitialized and get: ‘prev_order’ may be used uninitialized in this function [-Werror=maybe-uninitialized] Could fix also #6738 --- deps/ziptree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/ziptree.c b/deps/ziptree.c index 09655d10e..a136d411a 100644 --- a/deps/ziptree.c +++ b/deps/ziptree.c @@ -127,7 +127,7 @@ __ZIP_INSERT(void *h, zip_cmp_cb cmp, unsigned short fieldoffset, * than "x" */ zip_elem *prev = NULL; zip_elem *cur = head->root; - enum ZIP_CMP cur_order, prev_order; + enum ZIP_CMP cur_order, prev_order = ZIP_CMP_EQ; do { cur_order = __ZIP_UNIQUE_CMP(cmp, x_key, ZIP_KEY_PTR(cur)); if(cur_order == ZIP_CMP_EQ) From 6e1556dd9bb63e544359c6fc5a8c86b6c75f48fb Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Wed, 27 Nov 2024 14:25:31 +0100 Subject: [PATCH 082/158] refactor(server): Add preparations for avahi based mDNS plugin Currently a fork of mdnsd library is used to register and discover servers on the network. Since this fork is not maintained anymore a switch to another library is necessary. If avahi daemon is already running on the system, it can be used to register the servers on network. Add mDNS build option and a copy of mDNS file as preparation for an additional avahi based mDNS plugin. Signed-off-by: Vasilij Strassheim --- CMakeLists.txt | 82 +- doc/building.rst | 7 +- include/open62541/config.h.in | 6 +- src/server/ua_discovery_mdns_avahi.c | 1359 ++++++++++++++++++++++++++ 4 files changed, 1436 insertions(+), 18 deletions(-) create mode 100644 src/server/ua_discovery_mdns_avahi.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ba75f2e0c..bd1ae0712 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,8 +139,43 @@ mark_as_advanced(UA_ENABLE_PARSING) option(UA_ENABLE_INLINABLE_EXPORT "Export 'static inline' methods as regular API" OFF) mark_as_advanced(UA_ENABLE_INLINABLE_EXPORT) -option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF) +# mDNS provider +set(UA_MDNS_PLUGINS "MDNSD" "AVAHI") +set(UA_ENABLE_DISCOVERY_MULTICAST "OFF" CACHE STRING "mDNS discovery support") mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST) +SET_PROPERTY(CACHE UA_ENABLE_DISCOVERY_MULTICAST PROPERTY STRINGS "OFF" ${UA_MDNS_PLUGINS}) +option(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD "Enable mDNS discovery support (uses mdnsd)" OFF) +mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) +option(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI "Enable mDNS discovery support (uses Avahi)" OFF) +mark_as_advanced(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + +list (FIND UA_MDNS_PLUGINS ${UA_ENABLE_DISCOVERY_MULTICAST} _tmp) +if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "OFF" OR ${_tmp} GREATER -1) + set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF) + set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF) + if(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "MDNSD") + set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON) + elseif(UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "AVAHI") + set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI ON) + endif() +# Only for backward compatability +elseif(UA_ENABLE_DISCOVERY_MULTICAST OR UA_ENABLE_DISCOVERY_MULTICAST STREQUAL "ON") + message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." ) + if(NOT UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON) + endif() +else() + message(DEPRECATION "Set UA_ENABLE_DISCOVERY_MULTICAST to the desired mDNS library." ) + if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) + set(UA_ENABLE_DISCOVERY_MULTICAST "MDNSD") + set(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI OFF) + endif() + if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + set(UA_ENABLE_DISCOVERY_MULTICAST "AVAHI") + set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF) + endif() +endif() + # security provider set(UA_ENCRYPTION_PLUGINS "MBEDTLS" "OPENSSL" "LIBRESSL") @@ -231,7 +266,8 @@ endif() if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ OR UA_BUILD_FUZZING_CORPUS) # Force enable options not passed in the build script, to also fuzzy-test this code set(UA_ENABLE_DISCOVERY ON CACHE STRING "" FORCE) - set(UA_ENABLE_DISCOVERY_MULTICAST ON CACHE STRING "" FORCE) + set(UA_ENABLE_DISCOVERY_MULTICAST MDNSD CACHE STRING "" FORCE) + set(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD ON CACHE STRING "" FORCE) set(UA_ENABLE_ENCRYPTION ON CACHE STRING "OFF" FORCE) set(UA_ENABLE_ENCRYPTION_MBEDTLS ON CACHE STRING "" FORCE) set(UA_ENABLE_HISTORIZING ON CACHE STRING "" FORCE) @@ -252,7 +288,7 @@ endif() if(UA_ENABLE_DISCOVERY_MULTICAST AND NOT UA_ENABLE_DISCOVERY) MESSAGE(WARNING "UA_ENABLE_DISCOVERY_MULTICAST is enabled, but not UA_ENABLE_DISCOVERY. UA_ENABLE_DISCOVERY_MULTICAST will be set to OFF") - SET(UA_ENABLE_DISCOVERY_MULTICAST OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE) + SET(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE) endif() # Advanced options @@ -498,6 +534,13 @@ if(MINGW) list(APPEND open62541_LIBRARIES ws2_32 ssp) endif() +if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + find_package(PkgConfig REQUIRED) + pkg_search_module(AVAHI REQUIRED avahi-client avahi-common) + include_directories(${AVAHI_INCLUDE_DIRS}) + list(APPEND open62541_LIBRARIES "${AVAHI_LIBRARIES}") +endif() + ##################### # Compiler Settings # ##################### @@ -691,7 +734,7 @@ file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src_generated") # Generate the config.h configure_file(include/open62541/config.h.in ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h) -if(UA_ENABLE_DISCOVERY_MULTICAST) +if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) include(GenerateExportHeader) set(MDNSD_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported" FORCE) @@ -871,20 +914,24 @@ if(UA_DEBUG_DUMP_PKGS) list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/plugins/ua_debug_dump_pkgs.c) endif() -if(UA_ENABLE_DISCOVERY_MULTICAST) +if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) # prepend in list, otherwise it complains that winsock2.h has to be included before windows.h list(APPEND lib_headers - ${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h) + ${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h) list(APPEND lib_sources - ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c - ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c) + ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns.c + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c + ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c) +elseif(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + list(APPEND lib_sources + ${PROJECT_SOURCE_DIR}/src/server/ua_discovery_mdns_avahi.c + ) endif() if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ) @@ -1303,7 +1350,7 @@ add_library(open62541::open62541 ALIAS open62541) target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541-plugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT) -if(UA_ENABLE_DISCOVERY_MULTICAST) +if(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) target_compile_definitions(open62541-object PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT) target_compile_definitions(open62541 PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT) endif() @@ -1325,6 +1372,9 @@ SET_TARGET_PROPERTIES(open62541 PROPERTIES # DLL requires linking to dependencies target_link_libraries(open62541 PUBLIC ${open62541_PUBLIC_LIBRARIES}) target_link_libraries(open62541 PRIVATE ${open62541_LIBRARIES}) +if(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) + target_link_libraries(open62541 PUBLIC ${AVAHI_LIBRARIES}) +endif() ########################## # Build Selected Targets # diff --git a/doc/building.rst b/doc/building.rst index 497259646..0b1a04601 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -294,7 +294,12 @@ Detailed SDK Features Enable Discovery Service (LDS) **UA_ENABLE_DISCOVERY_MULTICAST** - Enable Discovery Service with multicast support (LDS-ME) + Enable Discovery Service with multicast support (LDS-ME) and specify the + multicast backend. The possible options are: + + - ``OFF`` No multicast support. (default) + - ``MDNSD`` Multicast support using libmdnsd + - ``AVAHI`` Multicast support using Avahi **UA_ENABLE_DISCOVERY_SEMAPHORE** Enable Discovery Semaphore support diff --git a/include/open62541/config.h.in b/include/open62541/config.h.in index 7901fa389..577a66ded 100644 --- a/include/open62541/config.h.in +++ b/include/open62541/config.h.in @@ -77,7 +77,11 @@ #cmakedefine UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS #cmakedefine UA_ENABLE_DETERMINISTIC_RNG #cmakedefine UA_ENABLE_DISCOVERY -#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST +#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_MDNSD +#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST_AVAHI +#if defined(UA_ENABLE_DISCOVERY_MULTICAST_MDNSD) || defined(UA_ENABLE_DISCOVERY_MULTICAST_AVAHI) +#define UA_ENABLE_DISCOVERY_MULTICAST +#endif #cmakedefine UA_ENABLE_QUERY #cmakedefine UA_ENABLE_MALLOC_SINGLETON #cmakedefine UA_ENABLE_DISCOVERY_SEMAPHORE diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c new file mode 100644 index 000000000..21da52cf3 --- /dev/null +++ b/src/server/ua_discovery_mdns_avahi.c @@ -0,0 +1,1359 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2017 (c) Stefan Profanter, fortiss GmbH + * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA + */ + +#include "ua_discovery.h" +#include "ua_server_internal.h" + +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_AVAHI + +#ifndef UA_ENABLE_AMALGAMATION +#include "mdnsd/libmdnsd/xht.h" +#include "mdnsd/libmdnsd/sdtxt.h" +#endif + +#include "../deps/mp_printf.h" + +#ifdef _WIN32 +/* inet_ntoa is deprecated on MSVC but used for compatibility */ +# define _WINSOCK_DEPRECATED_NO_WARNINGS +# include +# include +# include +#else +# include +# include +# include // for struct timeval +# include // for struct ip_mreq +# if defined(UA_HAS_GETIFADDR) +# include +# endif /* UA_HAS_GETIFADDR */ +# include /* for IFF_RUNNING */ +# include // for recvfrom in cygwin +#endif + +static struct serverOnNetwork * +mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, + UA_String serverName, UA_Boolean createNew) { + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, + strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + + while(hash_entry) { + size_t maxLen = serverName.length; + if(maxLen > hash_entry->entry->serverOnNetwork.serverName.length) + maxLen = hash_entry->entry->serverOnNetwork.serverName.length; + + if(strncmp((char*)hash_entry->entry->serverOnNetwork.serverName.data, + (char*)serverName.data, maxLen) == 0) + return hash_entry->entry; + hash_entry = hash_entry->next; + } + + if(!createNew) + return NULL; + + struct serverOnNetwork *listEntry; + UA_StatusCode res = + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, record, serverName, &listEntry); + if(res != UA_STATUSCODE_GOOD) + return NULL; + + return listEntry; +} + + +UA_StatusCode +UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + UA_String serverName, + struct serverOnNetwork **addedEntry) { + struct serverOnNetwork *entry = + mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, false); + if(entry) { + if(addedEntry != NULL) + *addedEntry = entry; + return UA_STATUSCODE_BADALREADYEXISTS; + } + + UA_LOG_DEBUG(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: Add entry to ServersOnNetwork: %s (%S)", + fqdnMdnsRecord, serverName); + + struct serverOnNetwork *listEntry = (serverOnNetwork*) + UA_malloc(sizeof(struct serverOnNetwork)); + if(!listEntry) + return UA_STATUSCODE_BADOUTOFMEMORY; + + + UA_EventLoop *el = dm->sc.server->config.eventLoop; + listEntry->created = el->dateTime_now(el); + listEntry->pathTmp = NULL; + listEntry->txtSet = false; + listEntry->srvSet = false; + UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); + listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; + UA_StatusCode res = UA_String_copy(&serverName, &listEntry->serverOnNetwork.serverName); + if(res != UA_STATUSCODE_GOOD) { + UA_free(listEntry); + return res; + } + dm->serverOnNetworkRecordIdCounter++; + if(dm->serverOnNetworkRecordIdCounter == 0) + dm->serverOnNetworkRecordIdLastReset = el->dateTime_now(el); + listEntry->lastSeen = el->dateTime_nowMonotonic(el); + + /* add to hash */ + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)fqdnMdnsRecord, + strlen(fqdnMdnsRecord)) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *newHashEntry = (struct serverOnNetwork_hash_entry*) + UA_malloc(sizeof(struct serverOnNetwork_hash_entry)); + if(!newHashEntry) { + UA_String_clear(&listEntry->serverOnNetwork.serverName); + UA_free(listEntry); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; + dm->serverOnNetworkHash[hashIdx] = newHashEntry; + newHashEntry->entry = listEntry; + + LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); + if(addedEntry != NULL) + *addedEntry = listEntry; + + return UA_STATUSCODE_GOOD; +} + +#ifdef _WIN32 + +/* see http://stackoverflow.com/a/10838854/869402 */ +static IP_ADAPTER_ADDRESSES * +getInterfaces(UA_DiscoveryManager *dm) { + IP_ADAPTER_ADDRESSES* adapter_addresses = NULL; + + /* Start with a 16 KB buffer and resize if needed - multiple attempts in + * case interfaces change while we are in the middle of querying them. */ + DWORD adapter_addresses_buffer_size = 16 * 1024; + for(size_t attempts = 0; attempts != 3; ++attempts) { + /* todo: malloc may fail: return a statuscode */ + adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size); + if(!adapter_addresses) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "GetAdaptersAddresses out of memory"); + adapter_addresses = NULL; + break; + } + DWORD error = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME, + NULL, adapter_addresses, + &adapter_addresses_buffer_size); + + if(ERROR_SUCCESS == error) { + break; + } else if (ERROR_BUFFER_OVERFLOW == error) { + /* Try again with the new size */ + UA_free(adapter_addresses); + adapter_addresses = NULL; + continue; + } + + /* Unexpected error */ + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "GetAdaptersAddresses returned an unexpected error. " + "Not setting mDNS A records."); + UA_free(adapter_addresses); + adapter_addresses = NULL; + break; + } + return adapter_addresses; +} + +#endif /* _WIN32 */ + +UA_StatusCode +UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + UA_String serverName) { + UA_LOG_DEBUG(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: Remove entry from ServersOnNetwork: %s (%S)", + fqdnMdnsRecord, serverName); + + struct serverOnNetwork *entry = + mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, false); + if(!entry) + return UA_STATUSCODE_BADNOTFOUND; + + UA_String recordStr; + // Cast away const because otherwise the pointer cannot be assigned. + // Be careful what you do with recordStr! + recordStr.data = (UA_Byte*)(uintptr_t)fqdnMdnsRecord; + recordStr.length = strlen(fqdnMdnsRecord); + + /* remove from hash */ + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, + recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; + struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *prevEntry = hash_entry; + while(hash_entry) { + if(hash_entry->entry == entry) { + if(dm->serverOnNetworkHash[hashIdx] == hash_entry) + dm->serverOnNetworkHash[hashIdx] = hash_entry->next; + else if(prevEntry) + prevEntry->next = hash_entry->next; + break; + } + prevEntry = hash_entry; + hash_entry = hash_entry->next; + } + UA_free(hash_entry); + + if(dm->serverOnNetworkCallback && + !UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr)) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, + entry->txtSet, + dm->serverOnNetworkCallbackData); + + /* Remove from list */ + LIST_REMOVE(entry, pointers); + UA_ServerOnNetwork_clear(&entry->serverOnNetwork); + if(entry->pathTmp) { + UA_free(entry->pathTmp); + entry->pathTmp = NULL; + } + UA_free(entry); + return UA_STATUSCODE_GOOD; +} + +static void +mdns_append_path_to_url(UA_String *url, const char *path) { + size_t pathLen = strlen(path); + size_t newUrlLen = url->length + pathLen; //size of the new url string incl. the path + /* todo: malloc may fail: return a statuscode */ + char *newUrl = (char *)UA_malloc(url->length + pathLen); + memcpy(newUrl, url->data, url->length); + memcpy(newUrl + url->length, path, pathLen); + UA_String_clear(url); + url->length = newUrlLen; + url->data = (UA_Byte *) newUrl; +} + +static void +setTxt(UA_DiscoveryManager *dm, const struct resource *r, + struct serverOnNetwork *entry) { + entry->txtSet = true; + xht_t *x = txt2sd(r->rdata, r->rdlength); + char *path = (char *) xht_get(x, "path"); + char *caps = (char *) xht_get(x, "caps"); + + size_t pathLen = path ? strlen(path) : 0; + + if(path && pathLen > 1) { + if(!entry->srvSet) { + /* txt arrived before SRV, thus cache path entry */ + if (!entry->pathTmp) { + entry->pathTmp = (char*)UA_malloc(pathLen+1); + if (!entry->pathTmp) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for mDNS srv path"); + return; + } + memcpy(entry->pathTmp, path, pathLen); + entry->pathTmp[pathLen] = '\0'; + } + } else { + /* SRV already there and discovery URL set. Add path to discovery URL */ + mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path); + } + } + + if(caps && strlen(caps) > 0) { + /* count comma in caps */ + size_t capsCount = 1; + for(size_t i = 0; caps[i]; i++) { + if(caps[i] == ',') + capsCount++; + } + + /* set capabilities */ + entry->serverOnNetwork.serverCapabilitiesSize = capsCount; + entry->serverOnNetwork.serverCapabilities = + (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); + + for(size_t i = 0; i < capsCount; i++) { + char *nextStr = strchr(caps, ','); + size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps); + entry->serverOnNetwork.serverCapabilities[i].length = len; + /* todo: malloc may fail: return a statuscode */ + entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len); + memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len); + if(nextStr) + caps = nextStr + 1; + else + break; + } + } + xht_free(x); +} + +/* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ +static void +setSrv(UA_DiscoveryManager *dm, const struct resource *r, + struct serverOnNetwork *entry) { + entry->srvSet = true; + + /* The specification Part 12 says: The hostname maps onto the SRV record + * target field. If the hostname is an IPAddress then it must be converted + * to a domain name. If this cannot be done then LDS shall report an + * error. */ + + size_t srvNameLen = strlen(r->known.srv.name); + if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.') + /* cut off last dot */ + srvNameLen--; + /* opc.tcp://[servername]:[port][path] */ + char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8 + 1); + if (!newUrl) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Cannot allocate char for discovery url. Out of memory."); + return; + } + + mp_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d", + (int)srvNameLen, r->known.srv.name, r->known.srv.port); + + entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: found server: %S", + entry->serverOnNetwork.discoveryUrl); + UA_free(newUrl); + + if(entry->pathTmp) { + mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp); + UA_free(entry->pathTmp); + entry->pathTmp = NULL; + } +} + +/* This will be called by the mDNS library on every record which is received */ +void +mdns_record_received(const struct resource *r, void *data) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) data; + + /* we only need SRV and TXT records */ + /* TODO: remove magic number */ + if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || + (r->type != QTYPE_SRV && r->type != QTYPE_TXT)) + return; + + /* we only handle '_opcua-tcp._tcp.' records */ + char *opcStr = strstr(r->name, "_opcua-tcp._tcp."); + if(!opcStr) + return; + + UA_String recordStr; + recordStr.data = (UA_Byte*)r->name; + recordStr.length = strlen(r->name); + UA_Boolean isSelfAnnounce = UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr); + if(isSelfAnnounce) + return; // ignore itself + + /* Extract the servername */ + size_t servernameLen = (size_t) (opcStr - r->name); + if(servernameLen == 0) + return; + servernameLen--; /* remove point */ + UA_String serverName = {servernameLen, (UA_Byte*)r->name}; + + /* Get entry */ + struct serverOnNetwork *entry = + mdns_record_add_or_get(dm, r->name, serverName, r->ttl > 0); + if(!entry) + return; + + /* Check that the ttl is positive */ + if(r->ttl == 0) { + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Multicast DNS: remove server (TTL=0): %S", + entry->serverOnNetwork.discoveryUrl); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, r->name, serverName); + return; + } + + /* Update lastSeen */ + UA_EventLoop *el = dm->sc.server->config.eventLoop; + entry->lastSeen = el->dateTime_nowMonotonic(el); + + /* TXT and SRV are already set */ + if(entry->txtSet && entry->srvSet) { + // call callback for every mdns package we received. + // This will also call the callback multiple times + if(dm->serverOnNetworkCallback) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, + dm->serverOnNetworkCallbackData); + return; + } + + /* Add the resources */ + if(r->type == QTYPE_TXT && !entry->txtSet) + setTxt(dm, r, entry); + else if (r->type == QTYPE_SRV && !entry->srvSet) + setSrv(dm, r, entry); + + /* Call callback to announce a new server */ + if(entry->srvSet && dm->serverOnNetworkCallback) + dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, + dm->serverOnNetworkCallbackData); +} + +void +mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *path, + const UA_String *capabilites, const size_t capabilitiesSize, + void (*conflict)(char *host, int type, void *arg)) { + mdns_record_t *r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + QTYPE_TXT, 600, conflict, dm); + xht_t *h = xht_new(11); + char *allocPath = NULL; + if(!path || strlen(path) == 0) { + xht_set(h, "path", "/"); + } else { + /* path does not contain slash, so add it here */ + size_t pathLen = strlen(path); + if(path[0] == '/') { + allocPath = (char*)UA_malloc(pathLen+1); + if(!allocPath) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for txt path"); + return; + } + memcpy(allocPath, path, pathLen); + allocPath[pathLen] = '\0'; + } else { + allocPath = (char*)UA_malloc(pathLen + 2); + if(!allocPath) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "Cannot alloc memory for txt path"); + return; + } + allocPath[0] = '/'; + memcpy(allocPath + 1, path, pathLen); + allocPath[pathLen + 1] = '\0'; + } + xht_set(h, "path", allocPath); + } + + /* calculate max string length: */ + size_t capsLen = 0; + for(size_t i = 0; i < capabilitiesSize; i++) { + /* add comma or last \0 */ + capsLen += capabilites[i].length + 1; + } + + char *caps = NULL; + if(capsLen) { + /* freed when xht_free is called */ + /* todo: malloc may fail: return a statuscode */ + caps = (char*)UA_malloc(sizeof(char) * capsLen); + size_t idx = 0; + for(size_t i = 0; i < capabilitiesSize; i++) { + memcpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length); + idx += capabilites[i].length + 1; + caps[idx - 1] = ','; + } + caps[idx - 1] = '\0'; + + xht_set(h, "caps", caps); + } else { + xht_set(h, "caps", "NA"); + } + + int txtRecordLength; + unsigned char *packet = sd2txt(h, &txtRecordLength); + if(allocPath) + UA_free(allocPath); + if(caps) + UA_free(caps); + xht_free(h); + mdnsd_set_raw(dm->mdnsDaemon, r, (char *) packet, + (unsigned short) txtRecordLength); + UA_free(packet); +} + +mdns_record_t * +mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, + const char *host, const char *rdname) { + mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); + if(!r) + return NULL; + + /* search for the record with the correct ptr hostname */ + while(r) { + const mdns_answer_t *data = mdnsd_record_data(r); + if(data->type == type && strcmp(data->rdname, rdname) == 0) + return r; + r = mdnsd_record_next(r); + } + return NULL; +} + +/* set record in the given interface */ +static void +mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain, char *addr, UA_UInt16 addr_len) { + /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ + mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); + mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); + + /* [hostname]. A [ip]. */ + r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); + mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); +} + +/* Loop over network interfaces and run set_address_record on each */ +#ifdef _WIN32 + +void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(dm); + if(!adapter_addresses) + return; + + /* Iterate through all of the adapters */ + IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; + for(; adapter != NULL; adapter = adapter->Next) { + /* Skip loopback adapters */ + if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) + continue; + + /* Parse all IPv4 and IPv6 addresses */ + IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; + for(; NULL != address; address = address->Next) { + int family = address->Address.lpSockaddr->sa_family; + if(AF_INET == family) { + SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ + mdns_set_address_record_if(dm, fullServiceDomain, + localDomain, (char *)&ipv4->sin_addr, 4); + } else if(AF_INET6 == family) { + /* IPv6 */ +#if 0 + SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr); + + char str_buffer[INET6_ADDRSTRLEN] = {0}; + inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); + + std::string ipv6_str(str_buffer); + + /* Detect and skip non-external addresses */ + UA_Boolean is_link_local(false); + UA_Boolean is_special_use(false); + + if(0 == ipv6_str.find("fe")) { + char c = ipv6_str[2]; + if(c == '8' || c == '9' || c == 'a' || c == 'b') + is_link_local = true; + } else if (0 == ipv6_str.find("2001:0:")) { + is_special_use = true; + } + + if(!(is_link_local || is_special_use)) + ipAddrs.mIpv6.push_back(ipv6_str); +#endif + } + } + } + + /* Cleanup */ + UA_free(adapter_addresses); + adapter_addresses = NULL; +} + +#elif defined(UA_HAS_GETIFADDR) + +void +mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + struct ifaddrs *ifaddr; + struct ifaddrs *ifa; + if(getifaddrs(&ifaddr) == -1) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "getifaddrs returned an unexpected error. Not setting mDNS A records."); + return; + } + + /* Walk through linked list, maintaining head pointer so we can free list later */ + int n; + for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { + if(!ifa->ifa_addr) + continue; + + if((strcmp("lo", ifa->ifa_name) == 0) || + !(ifa->ifa_flags & (IFF_RUNNING))|| + !(ifa->ifa_flags & (IFF_MULTICAST))) + continue; + + /* IPv4 */ + if(ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; + mdns_set_address_record_if(dm, fullServiceDomain, + localDomain, (char*)&sa->sin_addr.s_addr, 4); + } + + /* IPv6 not implemented yet */ + } + + /* Clean up */ + freeifaddrs(ifaddr); +} +#else /* _WIN32 */ + +void +mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, + const char *localDomain) { + if(dm->sc.server->config.mdnsIpAddressListSize == 0) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, + "If UA_HAS_GETIFADDR is false, config.mdnsIpAddressList must be set"); + return; + } + + for(size_t i=0; i< dm->sc.server->config.mdnsIpAddressListSize; i++) { + mdns_set_address_record_if(dm, fullServiceDomain, localDomain, + (char*)&dm->sc.server->config.mdnsIpAddressList[i], 4); + } +} + +#endif /* _WIN32 */ + +typedef enum { + UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ + UA_DISCOVERY_TLS /* OPC UA HTTPS mapping */ +} UA_DiscoveryProtocol; + +/* Create a mDNS Record for the given server info and adds it to the mDNS output + * queue. + * + * Additionally this method also adds the given server to the internal + * serversOnNetwork list so that a client finds it when calling + * FindServersOnNetwork. */ +static UA_StatusCode +UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, + const UA_String hostname, UA_UInt16 port, + const UA_String path, const UA_DiscoveryProtocol protocol, + UA_Boolean createTxt, const UA_String* capabilites, + const size_t capabilitiesSize, + UA_Boolean isSelf); + +/* Create a mDNS Record for the given server info with TTL=0 and adds it to the + * mDNS output queue. + * + * Additionally this method also removes the given server from the internal + * serversOnNetwork list so that a client gets the updated data when calling + * FindServersOnNetwork. */ +static UA_StatusCode +UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, + const UA_String hostname, UA_UInt16 port, + UA_Boolean removeTxt); + +static int +discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg); + +static void +mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, + UA_Boolean recv) { + if(!recv) { + dm->mdnsSendConnection = connectionId; + return; + } + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] == connectionId) + return; + } + + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] != 0) + continue; + dm->mdnsRecvConnections[i] = connectionId; + dm->mdnsRecvConnectionsSize++; + break; + } +} + +static void +mdnsRemoveConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, + UA_Boolean recv) { + if(dm->mdnsSendConnection == connectionId) { + dm->mdnsSendConnection = 0; + return; + } + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { + if(dm->mdnsRecvConnections[i] != connectionId) + continue; + dm->mdnsRecvConnections[i] = 0; + dm->mdnsRecvConnectionsSize--; + break; + } +} + +static void +MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *_, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg, UA_Boolean recv) { + UA_DiscoveryManager *dm = *(UA_DiscoveryManager**)connectionContext; + + if(state == UA_CONNECTIONSTATE_CLOSING) { + mdnsRemoveConnection(dm, connectionId, recv); + + /* Fully stopped? Internally checks if all sockets are closed. */ + UA_DiscoveryManager_setState(dm, dm->sc.state); + + /* Restart mdns sockets if not shutting down */ + if(dm->sc.state == UA_LIFECYCLESTATE_STARTED) + UA_DiscoveryManager_startMulticast(dm); + + return; + } + + mdnsAddConnection(dm, connectionId, recv); + + if(msg.length == 0) + return; + + /* Prepare the sockaddrinfo */ + const UA_UInt16 *port = (const UA_UInt16*) + UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-port"), + &UA_TYPES[UA_TYPES_UINT16]); + const UA_String *address = (const UA_String*) + UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-address"), + &UA_TYPES[UA_TYPES_STRING]); + if(!port || !address) + return; + + char portStr[16]; + UA_UInt16 myPort = *port; + for(size_t i = 0; i < 16; i++) { + if(myPort == 0) { + portStr[i] = 0; + break; + } + unsigned char rem = (unsigned char)(myPort % 10); + portStr[i] = (char)(rem + 48); /* to ascii */ + myPort = myPort / 10; + } + + struct addrinfo *infoptr; + int res = getaddrinfo((const char*)address->data, portStr, NULL, &infoptr); + if(res != 0) + return; + + /* Parse and process the message */ + struct message mm; + memset(&mm, 0, sizeof(struct message)); + UA_Boolean rr = message_parse(&mm, (unsigned char*)msg.data, msg.length); + if(rr) + mdnsd_in(dm->mdnsDaemon, &mm, infoptr->ai_addr, + (unsigned short)infoptr->ai_addrlen); + freeaddrinfo(infoptr); +} + +void +UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { + UA_ConnectionManager *cm = dm->cm; + if(!dm->cm || dm->mdnsSendConnection == 0) + return; + + struct sockaddr ip; + memset(&ip, 0, sizeof(struct sockaddr)); + ip.sa_family = AF_INET; /* Ipv4 */ + + struct message mm; + memset(&mm, 0, sizeof(struct message)); + + unsigned short sport = 0; + while(mdnsd_out(dm->mdnsDaemon, &mm, &ip, &sport) > 0) { + int len = message_packet_len(&mm); + char* buf = (char*)message_packet(&mm); + if(len <= 0) + continue; + UA_ByteString sendBuf = UA_BYTESTRING_NULL; + UA_StatusCode rv = cm->allocNetworkBuffer(cm, dm->mdnsSendConnection, + &sendBuf, (size_t)len); + if(rv != UA_STATUSCODE_GOOD) + continue; + memcpy(sendBuf.data, buf, sendBuf.length); + cm->sendWithConnection(cm, dm->mdnsSendConnection, + &UA_KEYVALUEMAP_NULL, &sendBuf); + } +} + +static void +MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *application, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg) { + MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, + state, params, msg, true); +} + +static void +MulticastDiscoverySendCallback(UA_ConnectionManager *cm, uintptr_t connectionId, + void *application, void **connectionContext, + UA_ConnectionState state, const UA_KeyValueMap *params, + UA_ByteString msg) { + MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, + state, params, msg, false); +} + +static UA_StatusCode +addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName, + const UA_String *discoveryUrl) { + UA_String hostname = UA_STRING_NULL; + char hoststr[256]; /* check with UA_MAXHOSTNAME_LENGTH */ + UA_UInt16 port = 4840; + UA_String path = UA_STRING_NULL; + UA_StatusCode retval = + UA_parseEndpointUrl(discoveryUrl, &hostname, &port, &path); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Server url is invalid: %S", *discoveryUrl); + return retval; + } + + if(hostname.length == 0) { + gethostname(hoststr, sizeof(hoststr)-1); + hoststr[sizeof(hoststr)-1] = '\0'; + hostname.data = (unsigned char *) hoststr; + hostname.length = strlen(hoststr); + } + retval = UA_Discovery_addRecord(dm, serverName, hostname, port, path, UA_DISCOVERY_TCP, true, + dm->sc.server->config.mdnsConfig.serverCapabilities, + dm->sc.server->config.mdnsConfig.serverCapabilitiesSize, true); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Cannot add mDNS Record: %s", UA_StatusCode_name(retval)); + return retval; + } + return UA_STATUSCODE_GOOD; +} + +#ifndef IN_ZERONET +#define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) +#endif + +/* Create multicast 224.0.0.251:5353 socket */ +static void +discovery_createMulticastSocket(UA_DiscoveryManager *dm) { + /* Find the connection manager */ + if(!dm->cm) { + UA_String udpString = UA_STRING("udp"); + for(UA_EventSource *es = dm->sc.server->config.eventLoop->eventSources; + es != NULL; es = es->next) { + /* Is this a usable connection manager? */ + if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) + continue; + UA_ConnectionManager *cm = (UA_ConnectionManager*)es; + if(UA_String_equal(&udpString, &cm->protocol)) { + dm->cm = cm; + break; + } + } + } + + if(!dm->cm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "No UDP communication supported"); + return; + } + + /* Set up the parameters */ + UA_KeyValuePair params[6]; + size_t paramsSize = 5; + + UA_UInt16 port = 5353; + UA_String address = UA_STRING("224.0.0.251"); + UA_UInt32 ttl = 255; + UA_Boolean reuse = true; + UA_Boolean listen = true; + + params[0].key = UA_QUALIFIEDNAME(0, "port"); + UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); + params[1].key = UA_QUALIFIEDNAME(0, "address"); + UA_Variant_setScalar(¶ms[1].value, &address, &UA_TYPES[UA_TYPES_STRING]); + params[2].key = UA_QUALIFIEDNAME(0, "listen"); + UA_Variant_setScalar(¶ms[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); + params[3].key = UA_QUALIFIEDNAME(0, "reuse"); + UA_Variant_setScalar(¶ms[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); + params[4].key = UA_QUALIFIEDNAME(0, "ttl"); + UA_Variant_setScalar(¶ms[4].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); + if(dm->sc.server->config.mdnsInterfaceIP.length > 0) { + params[5].key = UA_QUALIFIEDNAME(0, "interface"); + UA_Variant_setScalar(¶ms[5].value, &dm->sc.server->config.mdnsInterfaceIP, + &UA_TYPES[UA_TYPES_STRING]); + paramsSize++; + } + + /* Open the listen connection */ + UA_KeyValueMap kvm = {paramsSize, params}; + UA_StatusCode res = UA_STATUSCODE_GOOD; + + if(dm->mdnsRecvConnectionsSize == 0) { + res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, + MulticastDiscoveryRecvCallback); + if(res != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Could not create the mdns UDP multicast listen connection"); + } + + /* Open the send connection */ + listen = false; + if(dm->mdnsSendConnection == 0) { + res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, + MulticastDiscoverySendCallback); + if(res != UA_STATUSCODE_GOOD) + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Could not create the mdns UDP multicast send connection"); + } +} + +void +UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { + if(!dm->mdnsDaemon) { + dm->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); + mdnsd_register_receive_callback(dm->mdnsDaemon, mdns_record_received, dm); + } + +#if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + + /* Open the mdns listen socket */ + if(dm->mdnsSendConnection == 0) + discovery_createMulticastSocket(dm); + if(dm->mdnsSendConnection == 0) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Could not create multicast socket"); + return; + } + + /* Add record for the server itself */ + UA_String appName = dm->sc.server->config.mdnsConfig.mdnsServerName; + for(size_t i = 0; i < dm->sc.server->config.serverUrlsSize; i++) + addMdnsRecordForNetworkLayer(dm, appName, &dm->sc.server->config.serverUrls[i]); + + /* Send a multicast probe to find any other OPC UA server on the network + * through mDNS */ + mdnsd_query(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + QTYPE_PTR,discovery_multicastQueryAnswer, dm->sc.server); +} + +void +UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { + UA_Server *server = dm->sc.server; + for(size_t i = 0; i < server->config.serverUrlsSize; i++) { + UA_String hostname = UA_STRING_NULL; + UA_String path = UA_STRING_NULL; + UA_UInt16 port = 0; + + UA_StatusCode retval = + UA_parseEndpointUrl(&server->config.serverUrls[i], + &hostname, &port, &path); + + if(retval != UA_STATUSCODE_GOOD || hostname.length == 0) + continue; + + UA_Discovery_removeRecord(dm, server->config.mdnsConfig.mdnsServerName, + hostname, port, true); + } + + /* Stop the cyclic polling callback */ + if(dm->mdnsCallbackId != 0) { + UA_EventLoop *el = server->config.eventLoop; + if(el) { + el->removeTimer(el, dm->mdnsCallbackId); + dm->mdnsCallbackId = 0; + } + } + + /* Close the socket */ + if(dm->cm) { + if(dm->mdnsSendConnection) + dm->cm->closeConnection(dm->cm, dm->mdnsSendConnection); + for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) + if(dm->mdnsRecvConnections[i] != 0) + dm->cm->closeConnection(dm->cm, dm->mdnsRecvConnections[i]); + } +} + +void +UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String serverName, + const UA_MdnsDiscoveryConfiguration *mdnsConfig, + const UA_String discoveryUrl, + UA_Boolean isOnline, UA_Boolean updateTxt) { + UA_String hostname = UA_STRING_NULL; + UA_UInt16 port = 4840; + UA_String path = UA_STRING_NULL; + UA_StatusCode retval = + UA_parseEndpointUrl(&discoveryUrl, &hostname, &port, &path); + if(retval != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Server url invalid: %S", discoveryUrl); + return; + } + + if(!isOnline) { + UA_StatusCode removeRetval = + UA_Discovery_removeRecord(dm, serverName, hostname, + port, updateTxt); + if(removeRetval != UA_STATUSCODE_GOOD) + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Could not remove mDNS record for hostname %S", serverName); + return; + } + + UA_String *capabilities = NULL; + size_t capabilitiesSize = 0; + if(mdnsConfig) { + capabilities = mdnsConfig->serverCapabilities; + capabilitiesSize = mdnsConfig->serverCapabilitiesSize; + } + + UA_StatusCode addRetval = + UA_Discovery_addRecord(dm, serverName, hostname, + port, path, UA_DISCOVERY_TCP, updateTxt, + capabilities, capabilitiesSize, false); + if(addRetval != UA_STATUSCODE_GOOD) + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Could not add mDNS record for hostname %S", serverName); +} + +void +UA_Server_setServerOnNetworkCallback(UA_Server *server, + UA_Server_serverOnNetworkCallback cb, + void* data) { + UA_LOCK(&server->serviceMutex); + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(dm) { + dm->serverOnNetworkCallback = cb; + dm->serverOnNetworkCallbackData = data; + } + UA_UNLOCK(&server->serviceMutex); +} + +static void +UA_Discovery_multicastConflict(char *name, int type, void *arg) { + /* In case logging is disabled */ + (void)name; + (void)type; + + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) arg; + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS name conflict detected: " + "'%s' for type %d", name, type); +} + +/* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ +static void +createFullServiceDomain(char *outServiceDomain, size_t maxLen, + UA_String servername, UA_String hostname) { + maxLen -= 24; /* the length we have remaining before the opc ua postfix and + * the trailing zero */ + + /* Can we use hostname and servername with full length? */ + if(hostname.length + servername.length + 1 > maxLen) { + if(servername.length + 2 > maxLen) { + servername.length = maxLen; + hostname.length = 0; + } else { + hostname.length = maxLen - servername.length - 1; + } + } + + size_t offset = 0; + if(hostname.length > 0) { + mp_snprintf(outServiceDomain, maxLen + 1, "%S-%S", servername, hostname); + offset = servername.length + hostname.length + 1; + //replace all dots with minus. Otherwise mDNS is not valid + for(size_t i = servername.length+1; i < offset; i++) { + if(outServiceDomain[i] == '.') + outServiceDomain[i] = '-'; + } + } else { + mp_snprintf(outServiceDomain, maxLen + 1, "%S", servername); + offset = servername.length; + } + mp_snprintf(&outServiceDomain[offset], 24, "._opcua-tcp._tcp.local."); +} + +/* Check if mDNS already has an entry for given hostname and port combination */ +static UA_Boolean +UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, + unsigned short port, const UA_DiscoveryProtocol protocol) { + // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. + mdns_record_t *r = mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + while(r) { + const mdns_answer_t *data = mdnsd_record_data(r); + if(data->type == QTYPE_SRV && (port == 0 || data->srv.port == port)) + return true; + r = mdnsd_record_next(r); + } + return false; +} + +static int +discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) { + UA_Server *server = (UA_Server*) arg; + UA_DiscoveryManager *dm = (UA_DiscoveryManager*) + getServerComponentByName(server, UA_STRING("discovery")); + if(!dm) + return 0; + + if(a->type != QTYPE_PTR) + return 0; + + if(a->rdname == NULL) + return 0; + + /* Skip, if we already know about this server */ + UA_Boolean exists = + UA_Discovery_recordExists(dm, a->rdname, 0, UA_DISCOVERY_TCP); + if(exists == true) + return 0; + + if(mdnsd_has_query(dm->mdnsDaemon, a->rdname)) + return 0; + + UA_LOG_DEBUG(server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname); + + mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_SRV, + discovery_multicastQueryAnswer, server); + mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_TXT, + discovery_multicastQueryAnswer, server); + return 0; +} + +static UA_StatusCode +UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, + const UA_String hostname, UA_UInt16 port, + const UA_String path, const UA_DiscoveryProtocol protocol, + UA_Boolean createTxt, const UA_String* capabilites, + const size_t capabilitiesSize, + UA_Boolean isSelf) { + /* We assume that the hostname is not an IP address, but a valid domain + * name. It is required by the OPC UA spec (see Part 12, DiscoveryURL to DNS + * SRV mapping) to always use the hostname instead of the IP address. */ + + if(capabilitiesSize > 0 && !capabilites) + return UA_STATUSCODE_BADINVALIDARGUMENT; + + if(hostname.length == 0 || servername.length == 0) + return UA_STATUSCODE_BADOUTOFRANGE; + + /* Use a limit for the hostname length to make sure full string fits into 63 + * chars (limited by DNS spec) */ + if(hostname.length + servername.length + 1 > 63) { /* include dash between servername-hostname */ + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Combination of hostname+servername exceeds " + "maximum of 62 chars. It will be truncated."); + } else if(hostname.length > 63) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Hostname length exceeds maximum of 63 chars. " + "It will be truncated."); + } + + if(!dm->mdnsMainSrvAdded) { + mdns_record_t *r = + mdnsd_shared(dm->mdnsDaemon, "_services._dns-sd._udp.local.", + QTYPE_PTR, 600); + mdnsd_set_host(dm->mdnsDaemon, r, "_opcua-tcp._tcp.local."); + dm->mdnsMainSrvAdded = true; + } + + /* [servername]-[hostname]._opcua-tcp._tcp.local. */ + char fullServiceDomain[63+24]; + createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + + UA_Boolean exists = UA_Discovery_recordExists(dm, fullServiceDomain, + port, protocol); + if(exists == true) + return UA_STATUSCODE_GOOD; + + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: add record for domain: %s", fullServiceDomain); + + if(isSelf && dm->selfFqdnMdnsRecord.length == 0) { + dm->selfFqdnMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); + if(!dm->selfFqdnMdnsRecord.data) + return UA_STATUSCODE_BADOUTOFMEMORY; + } + + UA_String serverName = { + UA_MIN(63, servername.length + hostname.length + 1), + (UA_Byte*) fullServiceDomain}; + + struct serverOnNetwork *listEntry; + /* The servername is servername + hostname. It is the same which we get + * through mDNS and therefore we need to match servername */ + UA_StatusCode retval = + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, fullServiceDomain, + serverName, &listEntry); + if(retval != UA_STATUSCODE_GOOD && + retval != UA_STATUSCODE_BADALREADYEXISTS) + return retval; + + /* If entry is already in list, skip initialization of capabilities and txt+srv */ + if(retval != UA_STATUSCODE_BADALREADYEXISTS) { + /* if capabilitiesSize is 0, then add default cap 'NA' */ + listEntry->serverOnNetwork.serverCapabilitiesSize = UA_MAX(1, capabilitiesSize); + listEntry->serverOnNetwork.serverCapabilities = (UA_String *) + UA_Array_new(listEntry->serverOnNetwork.serverCapabilitiesSize, + &UA_TYPES[UA_TYPES_STRING]); + if(!listEntry->serverOnNetwork.serverCapabilities) + return UA_STATUSCODE_BADOUTOFMEMORY; + if(capabilitiesSize == 0) { + UA_String na; + na.length = 2; + na.data = (UA_Byte *) (uintptr_t) "NA"; + UA_String_copy(&na, &listEntry->serverOnNetwork.serverCapabilities[0]); + } else { + for(size_t i = 0; i < capabilitiesSize; i++) + UA_String_copy(&capabilites[i], + &listEntry->serverOnNetwork.serverCapabilities[i]); + } + + listEntry->txtSet = true; + + const size_t newUrlSize = 10 + hostname.length + 8 + path.length + 1; + UA_STACKARRAY(char, newUrl, newUrlSize); + memset(newUrl, 0, newUrlSize); + if(path.length > 0) { + mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S:%d/%S", hostname, port, path); + } else { + mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S:%d", hostname, port); + } + listEntry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); + listEntry->srvSet = true; + } + + /* _services._dns-sd._udp.local. PTR _opcua-tcp._tcp.local */ + + /* check if there is already a PTR entry for the given service. */ + + /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ + mdns_record_t *r = + mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + "_opcua-tcp._tcp.local.", fullServiceDomain); + if(!r) { + r = mdnsd_shared(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + QTYPE_PTR, 600); + mdnsd_set_host(dm->mdnsDaemon, r, fullServiceDomain); + } + + /* The first 63 characters of the hostname (or less) */ + size_t maxHostnameLen = UA_MIN(hostname.length, 63); + char localDomain[65]; + memcpy(localDomain, hostname.data, maxHostnameLen); + localDomain[maxHostnameLen] = '.'; + localDomain[maxHostnameLen+1] = '\0'; + + /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ + r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + QTYPE_SRV, 600, UA_Discovery_multicastConflict, dm); + mdnsd_set_srv(dm->mdnsDaemon, r, 0, 0, port, localDomain); + + /* A/AAAA record for all ip addresses. + * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. + * [hostname]. A [ip]. */ + mdns_set_address_record(dm, fullServiceDomain, localDomain); + + /* TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ + UA_STACKARRAY(char, pathChars, path.length + 1); + if(createTxt) { + if(path.length > 0) + memcpy(pathChars, path.data, path.length); + pathChars[path.length] = 0; + mdns_create_txt(dm, fullServiceDomain, pathChars, capabilites, + capabilitiesSize, UA_Discovery_multicastConflict); + } + + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, + const UA_String hostname, UA_UInt16 port, + UA_Boolean removeTxt) { + /* use a limit for the hostname length to make sure full string fits into 63 + * chars (limited by DNS spec) */ + if(hostname.length == 0 || servername.length == 0) + return UA_STATUSCODE_BADOUTOFRANGE; + + if(hostname.length + servername.length + 1 > 63) { /* include dash between servername-hostname */ + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: Combination of hostname+servername exceeds " + "maximum of 62 chars. It will be truncated."); + } + + /* [servername]-[hostname]._opcua-tcp._tcp.local. */ + char fullServiceDomain[63 + 24]; + createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: remove record for domain: %s", + fullServiceDomain); + + UA_String serverName = + {UA_MIN(63, servername.length + hostname.length + 1), (UA_Byte*)fullServiceDomain}; + + UA_StatusCode retval = + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, fullServiceDomain, serverName); + if(retval != UA_STATUSCODE_GOOD) + return retval; + + /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ + mdns_record_t *r = + mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + "_opcua-tcp._tcp.local.", fullServiceDomain); + if(!r) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: could not remove record. " + "PTR Record not found for domain: %s", fullServiceDomain); + return UA_STATUSCODE_BADNOTHINGTODO; + } + mdnsd_done(dm->mdnsDaemon, r); + + /* looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 + * port hostname.local. and TXT record: + * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... + * and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip] */ + mdns_record_t *r2 = + mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + if(!r2) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS: could not remove record. Record not " + "found for domain: %s", fullServiceDomain); + return UA_STATUSCODE_BADNOTHINGTODO; + } + + while(r2) { + const mdns_answer_t *data = mdnsd_record_data(r2); + mdns_record_t *next = mdnsd_record_next(r2); + if((removeTxt && data->type == QTYPE_TXT) || + (removeTxt && data->type == QTYPE_A) || + data->srv.port == port) { + mdnsd_done(dm->mdnsDaemon, r2); + } + r2 = next; + } + + return UA_STATUSCODE_GOOD; +} + +#endif /* UA_ENABLE_DISCOVERY_MULTICAST */ From f11e1e7e72f621e231511be6b2f6d7d19d3dcf47 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Mon, 2 Dec 2024 11:32:11 +0100 Subject: [PATCH 083/158] refactor(server): Remove mdnsd related code from mDNS copy To keep track of the differences of the mDNS plugins, we use the basis of the previous mDNS implementation and gradually adapt it to the Avahi implementation. Remove the mdnsd code. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery_mdns_avahi.c | 646 +-------------------------- 1 file changed, 1 insertion(+), 645 deletions(-) diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index 21da52cf3..6b37f7d8c 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -12,30 +12,8 @@ #ifdef UA_ENABLE_DISCOVERY_MULTICAST_AVAHI -#ifndef UA_ENABLE_AMALGAMATION -#include "mdnsd/libmdnsd/xht.h" -#include "mdnsd/libmdnsd/sdtxt.h" -#endif - #include "../deps/mp_printf.h" -#ifdef _WIN32 -/* inet_ntoa is deprecated on MSVC but used for compatibility */ -# define _WINSOCK_DEPRECATED_NO_WARNINGS -# include -# include -# include -#else -# include -# include -# include // for struct timeval -# include // for struct ip_mreq -# if defined(UA_HAS_GETIFADDR) -# include -# endif /* UA_HAS_GETIFADDR */ -# include /* for IFF_RUNNING */ -# include // for recvfrom in cygwin -#endif static struct serverOnNetwork * mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, @@ -244,275 +222,11 @@ mdns_append_path_to_url(UA_String *url, const char *path) { url->data = (UA_Byte *) newUrl; } -static void -setTxt(UA_DiscoveryManager *dm, const struct resource *r, - struct serverOnNetwork *entry) { - entry->txtSet = true; - xht_t *x = txt2sd(r->rdata, r->rdlength); - char *path = (char *) xht_get(x, "path"); - char *caps = (char *) xht_get(x, "caps"); - - size_t pathLen = path ? strlen(path) : 0; - - if(path && pathLen > 1) { - if(!entry->srvSet) { - /* txt arrived before SRV, thus cache path entry */ - if (!entry->pathTmp) { - entry->pathTmp = (char*)UA_malloc(pathLen+1); - if (!entry->pathTmp) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for mDNS srv path"); - return; - } - memcpy(entry->pathTmp, path, pathLen); - entry->pathTmp[pathLen] = '\0'; - } - } else { - /* SRV already there and discovery URL set. Add path to discovery URL */ - mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path); - } - } - - if(caps && strlen(caps) > 0) { - /* count comma in caps */ - size_t capsCount = 1; - for(size_t i = 0; caps[i]; i++) { - if(caps[i] == ',') - capsCount++; - } - - /* set capabilities */ - entry->serverOnNetwork.serverCapabilitiesSize = capsCount; - entry->serverOnNetwork.serverCapabilities = - (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]); - - for(size_t i = 0; i < capsCount; i++) { - char *nextStr = strchr(caps, ','); - size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps); - entry->serverOnNetwork.serverCapabilities[i].length = len; - /* todo: malloc may fail: return a statuscode */ - entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte*)UA_malloc(len); - memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len); - if(nextStr) - caps = nextStr + 1; - else - break; - } - } - xht_free(x); -} - -/* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ -static void -setSrv(UA_DiscoveryManager *dm, const struct resource *r, - struct serverOnNetwork *entry) { - entry->srvSet = true; - - /* The specification Part 12 says: The hostname maps onto the SRV record - * target field. If the hostname is an IPAddress then it must be converted - * to a domain name. If this cannot be done then LDS shall report an - * error. */ - - size_t srvNameLen = strlen(r->known.srv.name); - if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.') - /* cut off last dot */ - srvNameLen--; - /* opc.tcp://[servername]:[port][path] */ - char *newUrl = (char*)UA_malloc(10 + srvNameLen + 8 + 1); - if (!newUrl) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Cannot allocate char for discovery url. Out of memory."); - return; - } - - mp_snprintf(newUrl, 10 + srvNameLen + 8, "opc.tcp://%.*s:%d", - (int)srvNameLen, r->known.srv.name, r->known.srv.port); - - entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); - UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: found server: %S", - entry->serverOnNetwork.discoveryUrl); - UA_free(newUrl); - - if(entry->pathTmp) { - mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp); - UA_free(entry->pathTmp); - entry->pathTmp = NULL; - } -} - -/* This will be called by the mDNS library on every record which is received */ -void -mdns_record_received(const struct resource *r, void *data) { - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) data; - - /* we only need SRV and TXT records */ - /* TODO: remove magic number */ - if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) || - (r->type != QTYPE_SRV && r->type != QTYPE_TXT)) - return; - - /* we only handle '_opcua-tcp._tcp.' records */ - char *opcStr = strstr(r->name, "_opcua-tcp._tcp."); - if(!opcStr) - return; - - UA_String recordStr; - recordStr.data = (UA_Byte*)r->name; - recordStr.length = strlen(r->name); - UA_Boolean isSelfAnnounce = UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr); - if(isSelfAnnounce) - return; // ignore itself - - /* Extract the servername */ - size_t servernameLen = (size_t) (opcStr - r->name); - if(servernameLen == 0) - return; - servernameLen--; /* remove point */ - UA_String serverName = {servernameLen, (UA_Byte*)r->name}; - - /* Get entry */ - struct serverOnNetwork *entry = - mdns_record_add_or_get(dm, r->name, serverName, r->ttl > 0); - if(!entry) - return; - - /* Check that the ttl is positive */ - if(r->ttl == 0) { - UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: remove server (TTL=0): %S", - entry->serverOnNetwork.discoveryUrl); - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, r->name, serverName); - return; - } - - /* Update lastSeen */ - UA_EventLoop *el = dm->sc.server->config.eventLoop; - entry->lastSeen = el->dateTime_nowMonotonic(el); - - /* TXT and SRV are already set */ - if(entry->txtSet && entry->srvSet) { - // call callback for every mdns package we received. - // This will also call the callback multiple times - if(dm->serverOnNetworkCallback) - dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, - dm->serverOnNetworkCallbackData); - return; - } - - /* Add the resources */ - if(r->type == QTYPE_TXT && !entry->txtSet) - setTxt(dm, r, entry); - else if (r->type == QTYPE_SRV && !entry->srvSet) - setSrv(dm, r, entry); - - /* Call callback to announce a new server */ - if(entry->srvSet && dm->serverOnNetworkCallback) - dm->serverOnNetworkCallback(&entry->serverOnNetwork, true, entry->txtSet, - dm->serverOnNetworkCallbackData); -} - -void -mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *path, - const UA_String *capabilites, const size_t capabilitiesSize, - void (*conflict)(char *host, int type, void *arg)) { - mdns_record_t *r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, - QTYPE_TXT, 600, conflict, dm); - xht_t *h = xht_new(11); - char *allocPath = NULL; - if(!path || strlen(path) == 0) { - xht_set(h, "path", "/"); - } else { - /* path does not contain slash, so add it here */ - size_t pathLen = strlen(path); - if(path[0] == '/') { - allocPath = (char*)UA_malloc(pathLen+1); - if(!allocPath) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for txt path"); - return; - } - memcpy(allocPath, path, pathLen); - allocPath[pathLen] = '\0'; - } else { - allocPath = (char*)UA_malloc(pathLen + 2); - if(!allocPath) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Cannot alloc memory for txt path"); - return; - } - allocPath[0] = '/'; - memcpy(allocPath + 1, path, pathLen); - allocPath[pathLen + 1] = '\0'; - } - xht_set(h, "path", allocPath); - } - - /* calculate max string length: */ - size_t capsLen = 0; - for(size_t i = 0; i < capabilitiesSize; i++) { - /* add comma or last \0 */ - capsLen += capabilites[i].length + 1; - } - - char *caps = NULL; - if(capsLen) { - /* freed when xht_free is called */ - /* todo: malloc may fail: return a statuscode */ - caps = (char*)UA_malloc(sizeof(char) * capsLen); - size_t idx = 0; - for(size_t i = 0; i < capabilitiesSize; i++) { - memcpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length); - idx += capabilites[i].length + 1; - caps[idx - 1] = ','; - } - caps[idx - 1] = '\0'; - - xht_set(h, "caps", caps); - } else { - xht_set(h, "caps", "NA"); - } - - int txtRecordLength; - unsigned char *packet = sd2txt(h, &txtRecordLength); - if(allocPath) - UA_free(allocPath); - if(caps) - UA_free(caps); - xht_free(h); - mdnsd_set_raw(dm->mdnsDaemon, r, (char *) packet, - (unsigned short) txtRecordLength); - UA_free(packet); -} - -mdns_record_t * -mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, - const char *host, const char *rdname) { - mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); - if(!r) - return NULL; - - /* search for the record with the correct ptr hostname */ - while(r) { - const mdns_answer_t *data = mdnsd_record_data(r); - if(data->type == type && strcmp(data->rdname, rdname) == 0) - return r; - r = mdnsd_record_next(r); - } - return NULL; -} - /* set record in the given interface */ static void mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *localDomain, char *addr, UA_UInt16 addr_len) { - /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ - mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); - /* [hostname]. A [ip]. */ - r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); } /* Loop over network interfaces and run set_address_record on each */ @@ -659,156 +373,6 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, const UA_String hostname, UA_UInt16 port, UA_Boolean removeTxt); -static int -discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg); - -static void -mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, - UA_Boolean recv) { - if(!recv) { - dm->mdnsSendConnection = connectionId; - return; - } - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] == connectionId) - return; - } - - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != 0) - continue; - dm->mdnsRecvConnections[i] = connectionId; - dm->mdnsRecvConnectionsSize++; - break; - } -} - -static void -mdnsRemoveConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, - UA_Boolean recv) { - if(dm->mdnsSendConnection == connectionId) { - dm->mdnsSendConnection = 0; - return; - } - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != connectionId) - continue; - dm->mdnsRecvConnections[i] = 0; - dm->mdnsRecvConnectionsSize--; - break; - } -} - -static void -MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *_, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg, UA_Boolean recv) { - UA_DiscoveryManager *dm = *(UA_DiscoveryManager**)connectionContext; - - if(state == UA_CONNECTIONSTATE_CLOSING) { - mdnsRemoveConnection(dm, connectionId, recv); - - /* Fully stopped? Internally checks if all sockets are closed. */ - UA_DiscoveryManager_setState(dm, dm->sc.state); - - /* Restart mdns sockets if not shutting down */ - if(dm->sc.state == UA_LIFECYCLESTATE_STARTED) - UA_DiscoveryManager_startMulticast(dm); - - return; - } - - mdnsAddConnection(dm, connectionId, recv); - - if(msg.length == 0) - return; - - /* Prepare the sockaddrinfo */ - const UA_UInt16 *port = (const UA_UInt16*) - UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-port"), - &UA_TYPES[UA_TYPES_UINT16]); - const UA_String *address = (const UA_String*) - UA_KeyValueMap_getScalar(params, UA_QUALIFIEDNAME(0, "remote-address"), - &UA_TYPES[UA_TYPES_STRING]); - if(!port || !address) - return; - - char portStr[16]; - UA_UInt16 myPort = *port; - for(size_t i = 0; i < 16; i++) { - if(myPort == 0) { - portStr[i] = 0; - break; - } - unsigned char rem = (unsigned char)(myPort % 10); - portStr[i] = (char)(rem + 48); /* to ascii */ - myPort = myPort / 10; - } - - struct addrinfo *infoptr; - int res = getaddrinfo((const char*)address->data, portStr, NULL, &infoptr); - if(res != 0) - return; - - /* Parse and process the message */ - struct message mm; - memset(&mm, 0, sizeof(struct message)); - UA_Boolean rr = message_parse(&mm, (unsigned char*)msg.data, msg.length); - if(rr) - mdnsd_in(dm->mdnsDaemon, &mm, infoptr->ai_addr, - (unsigned short)infoptr->ai_addrlen); - freeaddrinfo(infoptr); -} - -void -UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { - UA_ConnectionManager *cm = dm->cm; - if(!dm->cm || dm->mdnsSendConnection == 0) - return; - - struct sockaddr ip; - memset(&ip, 0, sizeof(struct sockaddr)); - ip.sa_family = AF_INET; /* Ipv4 */ - - struct message mm; - memset(&mm, 0, sizeof(struct message)); - - unsigned short sport = 0; - while(mdnsd_out(dm->mdnsDaemon, &mm, &ip, &sport) > 0) { - int len = message_packet_len(&mm); - char* buf = (char*)message_packet(&mm); - if(len <= 0) - continue; - UA_ByteString sendBuf = UA_BYTESTRING_NULL; - UA_StatusCode rv = cm->allocNetworkBuffer(cm, dm->mdnsSendConnection, - &sendBuf, (size_t)len); - if(rv != UA_STATUSCODE_GOOD) - continue; - memcpy(sendBuf.data, buf, sendBuf.length); - cm->sendWithConnection(cm, dm->mdnsSendConnection, - &UA_KEYVALUEMAP_NULL, &sendBuf); - } -} - -static void -MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *application, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg) { - MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, - state, params, msg, true); -} - -static void -MulticastDiscoverySendCallback(UA_ConnectionManager *cm, uintptr_t connectionId, - void *application, void **connectionContext, - UA_ConnectionState state, const UA_KeyValueMap *params, - UA_ByteString msg) { - MulticastDiscoveryCallback(cm, connectionId, application, connectionContext, - state, params, msg, false); -} - static UA_StatusCode addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName, const UA_String *discoveryUrl) { @@ -845,111 +409,14 @@ addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName #define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) #endif -/* Create multicast 224.0.0.251:5353 socket */ -static void -discovery_createMulticastSocket(UA_DiscoveryManager *dm) { - /* Find the connection manager */ - if(!dm->cm) { - UA_String udpString = UA_STRING("udp"); - for(UA_EventSource *es = dm->sc.server->config.eventLoop->eventSources; - es != NULL; es = es->next) { - /* Is this a usable connection manager? */ - if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) - continue; - UA_ConnectionManager *cm = (UA_ConnectionManager*)es; - if(UA_String_equal(&udpString, &cm->protocol)) { - dm->cm = cm; - break; - } - } - } - - if(!dm->cm) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "No UDP communication supported"); - return; - } - - /* Set up the parameters */ - UA_KeyValuePair params[6]; - size_t paramsSize = 5; - - UA_UInt16 port = 5353; - UA_String address = UA_STRING("224.0.0.251"); - UA_UInt32 ttl = 255; - UA_Boolean reuse = true; - UA_Boolean listen = true; - - params[0].key = UA_QUALIFIEDNAME(0, "port"); - UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); - params[1].key = UA_QUALIFIEDNAME(0, "address"); - UA_Variant_setScalar(¶ms[1].value, &address, &UA_TYPES[UA_TYPES_STRING]); - params[2].key = UA_QUALIFIEDNAME(0, "listen"); - UA_Variant_setScalar(¶ms[2].value, &listen, &UA_TYPES[UA_TYPES_BOOLEAN]); - params[3].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Variant_setScalar(¶ms[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - params[4].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_Variant_setScalar(¶ms[4].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - if(dm->sc.server->config.mdnsInterfaceIP.length > 0) { - params[5].key = UA_QUALIFIEDNAME(0, "interface"); - UA_Variant_setScalar(¶ms[5].value, &dm->sc.server->config.mdnsInterfaceIP, - &UA_TYPES[UA_TYPES_STRING]); - paramsSize++; - } - - /* Open the listen connection */ - UA_KeyValueMap kvm = {paramsSize, params}; - UA_StatusCode res = UA_STATUSCODE_GOOD; - - if(dm->mdnsRecvConnectionsSize == 0) { - res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, - MulticastDiscoveryRecvCallback); - if(res != UA_STATUSCODE_GOOD) - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Could not create the mdns UDP multicast listen connection"); - } - - /* Open the send connection */ - listen = false; - if(dm->mdnsSendConnection == 0) { - res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, - MulticastDiscoverySendCallback); - if(res != UA_STATUSCODE_GOOD) - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Could not create the mdns UDP multicast send connection"); - } -} - void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { - if(!dm->mdnsDaemon) { - dm->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); - mdnsd_register_receive_callback(dm->mdnsDaemon, mdns_record_received, dm); - } - -#if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - - /* Open the mdns listen socket */ - if(dm->mdnsSendConnection == 0) - discovery_createMulticastSocket(dm); - if(dm->mdnsSendConnection == 0) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Could not create multicast socket"); - return; - } /* Add record for the server itself */ UA_String appName = dm->sc.server->config.mdnsConfig.mdnsServerName; for(size_t i = 0; i < dm->sc.server->config.serverUrlsSize; i++) addMdnsRecordForNetworkLayer(dm, appName, &dm->sc.server->config.serverUrls[i]); - /* Send a multicast probe to find any other OPC UA server on the network - * through mDNS */ - mdnsd_query(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", - QTYPE_PTR,discovery_multicastQueryAnswer, dm->sc.server); } void @@ -979,15 +446,6 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { dm->mdnsCallbackId = 0; } } - - /* Close the socket */ - if(dm->cm) { - if(dm->mdnsSendConnection) - dm->cm->closeConnection(dm->cm, dm->mdnsSendConnection); - for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) - if(dm->mdnsRecvConnections[i] != 0) - dm->cm->closeConnection(dm->cm, dm->mdnsRecvConnections[i]); - } } void @@ -1095,49 +553,9 @@ createFullServiceDomain(char *outServiceDomain, size_t maxLen, static UA_Boolean UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, unsigned short port, const UA_DiscoveryProtocol protocol) { - // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. - mdns_record_t *r = mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); - while(r) { - const mdns_answer_t *data = mdnsd_record_data(r); - if(data->type == QTYPE_SRV && (port == 0 || data->srv.port == port)) - return true; - r = mdnsd_record_next(r); - } return false; } -static int -discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) { - UA_Server *server = (UA_Server*) arg; - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) - getServerComponentByName(server, UA_STRING("discovery")); - if(!dm) - return 0; - - if(a->type != QTYPE_PTR) - return 0; - - if(a->rdname == NULL) - return 0; - - /* Skip, if we already know about this server */ - UA_Boolean exists = - UA_Discovery_recordExists(dm, a->rdname, 0, UA_DISCOVERY_TCP); - if(exists == true) - return 0; - - if(mdnsd_has_query(dm->mdnsDaemon, a->rdname)) - return 0; - - UA_LOG_DEBUG(server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname); - - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_SRV, - discovery_multicastQueryAnswer, server); - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_TXT, - discovery_multicastQueryAnswer, server); - return 0; -} static UA_StatusCode UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, @@ -1169,10 +587,6 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, } if(!dm->mdnsMainSrvAdded) { - mdns_record_t *r = - mdnsd_shared(dm->mdnsDaemon, "_services._dns-sd._udp.local.", - QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, "_opcua-tcp._tcp.local."); dm->mdnsMainSrvAdded = true; } @@ -1242,19 +656,6 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, listEntry->srvSet = true; } - /* _services._dns-sd._udp.local. PTR _opcua-tcp._tcp.local */ - - /* check if there is already a PTR entry for the given service. */ - - /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ - mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, - "_opcua-tcp._tcp.local.", fullServiceDomain); - if(!r) { - r = mdnsd_shared(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", - QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, fullServiceDomain); - } /* The first 63 characters of the hostname (or less) */ size_t maxHostnameLen = UA_MIN(hostname.length, 63); @@ -1263,10 +664,6 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, localDomain[maxHostnameLen] = '.'; localDomain[maxHostnameLen+1] = '\0'; - /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ - r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, - QTYPE_SRV, 600, UA_Discovery_multicastConflict, dm); - mdnsd_set_srv(dm->mdnsDaemon, r, 0, 0, port, localDomain); /* A/AAAA record for all ip addresses. * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. @@ -1279,8 +676,6 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, if(path.length > 0) memcpy(pathChars, path.data, path.length); pathChars[path.length] = 0; - mdns_create_txt(dm, fullServiceDomain, pathChars, capabilites, - capabilitiesSize, UA_Discovery_multicastConflict); } return UA_STATUSCODE_GOOD; @@ -1314,46 +709,7 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, UA_StatusCode retval = UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, fullServiceDomain, serverName); - if(retval != UA_STATUSCODE_GOOD) - return retval; - - /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ - mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, - "_opcua-tcp._tcp.local.", fullServiceDomain); - if(!r) { - UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: could not remove record. " - "PTR Record not found for domain: %s", fullServiceDomain); - return UA_STATUSCODE_BADNOTHINGTODO; - } - mdnsd_done(dm->mdnsDaemon, r); - - /* looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 - * port hostname.local. and TXT record: - * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... - * and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip] */ - mdns_record_t *r2 = - mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); - if(!r2) { - UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: could not remove record. Record not " - "found for domain: %s", fullServiceDomain); - return UA_STATUSCODE_BADNOTHINGTODO; - } - - while(r2) { - const mdns_answer_t *data = mdnsd_record_data(r2); - mdns_record_t *next = mdnsd_record_next(r2); - if((removeTxt && data->type == QTYPE_TXT) || - (removeTxt && data->type == QTYPE_A) || - data->srv.port == port) { - mdnsd_done(dm->mdnsDaemon, r2); - } - r2 = next; - } - - return UA_STATUSCODE_GOOD; + return retval; } #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ From a8930fd50a9da4b9a411d6ef311ade3bfbc6026c Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Mon, 2 Dec 2024 11:34:25 +0100 Subject: [PATCH 084/158] refactor(server): Clean up mDNS module Separate mDNS related code from discovery module to enable easier integration of different mDNS implementations. Add new API functions and make other functions static to reflect their use in the mDNS module only. The libmdnsd implementation uses an ip entry for the server and selects the interface to be used for establishing the connection. This is not required for avahi as the avahi daemon is normally configured externally. Make the server configuration of the mDNS IP list and the interface dependent on the use of libmdnsd. Signed-off-by: Vasilij Strassheim --- examples/discovery/server_multicast.c | 4 +- include/open62541/server.h | 4 +- plugins/ua_config_default.c | 4 +- plugins/ua_config_json.c | 6 +- src/server/ua_discovery.c | 37 +-- src/server/ua_discovery.h | 98 +++----- src/server/ua_discovery_mdns.c | 311 ++++++++++++++++++++------ src/server/ua_discovery_mdns_avahi.c | 199 +++------------- src/server/ua_server_config.c | 2 + src/server/ua_services_discovery.c | 29 ++- 10 files changed, 324 insertions(+), 370 deletions(-) diff --git a/examples/discovery/server_multicast.c b/examples/discovery/server_multicast.c index 66e8bac4b..af4d6f3fd 100644 --- a/examples/discovery/server_multicast.c +++ b/examples/discovery/server_multicast.c @@ -240,8 +240,10 @@ int main(int argc, char **argv) { config->mdnsConfig.mdnsServerName = UA_String_fromChars("Sample-Multicast-Server"); - //setting custom outbound interface +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD + //setting custom outbound interface for libmdnsd config->mdnsInterfaceIP = UA_String_fromChars("0.0.0.0"); +#endif // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv // For a LDS server, you should only indicate the LDS capability. diff --git a/include/open62541/server.h b/include/open62541/server.h index 7cc49a4ce..c889df6c6 100644 --- a/include/open62541/server.h +++ b/include/open62541/server.h @@ -288,10 +288,12 @@ struct UA_ServerConfig { # ifdef UA_ENABLE_DISCOVERY_MULTICAST UA_Boolean mdnsEnabled; UA_MdnsDiscoveryConfiguration mdnsConfig; +# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD UA_String mdnsInterfaceIP; -# if !defined(UA_HAS_GETIFADDR) +# if !defined(UA_HAS_GETIFADDR) size_t mdnsIpAddressListSize; UA_UInt32 *mdnsIpAddressList; +# endif # endif # endif #endif diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index 0d593903a..6fb4622f4 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -379,10 +379,12 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) { #ifdef UA_ENABLE_DISCOVERY_MULTICAST UA_MdnsDiscoveryConfiguration_clear(&conf->mdnsConfig); +# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD conf->mdnsInterfaceIP = UA_STRING_NULL; -# if !defined(UA_HAS_GETIFADDR) +# if !defined(UA_HAS_GETIFADDR) conf->mdnsIpAddressList = NULL; conf->mdnsIpAddressListSize = 0; +# endif # endif #endif diff --git a/plugins/ua_config_json.c b/plugins/ua_config_json.c index 93f727807..3b5d12d23 100644 --- a/plugins/ua_config_json.c +++ b/plugins/ua_config_json.c @@ -449,6 +449,7 @@ PARSE_JSON(MdnsConfigurationField) { parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsConfig.mdnsServerName, NULL); else if(strcmp(field_str, "serverCapabilities") == 0) parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRINGARRAY](ctx, &config->mdnsConfig.serverCapabilities, &config->mdnsConfig.serverCapabilitiesSize); +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD else if(strcmp(field_str, "mdnsInterfaceIP") == 0) parseJsonJumpTable[UA_SERVERCONFIGFIELD_STRING](ctx, &config->mdnsInterfaceIP, NULL); /* mdnsIpAddressList and mdnsIpAddressListSize are only available if UA_HAS_GETIFADDR is not defined: */ @@ -456,6 +457,7 @@ PARSE_JSON(MdnsConfigurationField) { else if(strcmp(field_str, "mdnsIpAddressList") == 0) parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize); # endif +#endif else { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown field name."); } @@ -957,10 +959,6 @@ parseJSONConfig(UA_ServerConfig *config, UA_ByteString json_config) { retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_BOOLEAN](&ctx, &config->mdnsEnabled, NULL); else if(strcmp(field, "mdns") == 0) retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_MDNSCONFIGURATION](&ctx, config, NULL); -#if !defined(UA_HAS_GETIFADDR) - else if(strcmp(field, "mdnsIpAddressList") == 0) - retval = parseJsonJumpTable[UA_SERVERCONFIGFIELD_UINT32ARRAY](&ctx, &config->mdnsIpAddressList, &config->mdnsIpAddressListSize); -#endif #endif #endif diff --git a/src/server/ua_discovery.c b/src/server/ua_discovery.c index 2e0483f62..e6a3588c6 100644 --- a/src/server/ua_discovery.c +++ b/src/server/ua_discovery.c @@ -26,8 +26,8 @@ UA_DiscoveryManager_setState(UA_DiscoveryManager *dm, if(state == UA_LIFECYCLESTATE_STOPPING || state == UA_LIFECYCLESTATE_STOPPED) { state = UA_LIFECYCLESTATE_STOPPED; -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - if(dm->mdnsRecvConnectionsSize != 0 || dm->mdnsSendConnection != 0) +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD + if(UA_DiscoveryManager_getMdnsConnectionCount() > 0) state = UA_LIFECYCLESTATE_STOPPING; #endif @@ -66,32 +66,7 @@ UA_DiscoveryManager_clear(struct UA_ServerComponent *sc) { } # ifdef UA_ENABLE_DISCOVERY_MULTICAST - serverOnNetwork *son, *son_tmp; - LIST_FOREACH_SAFE(son, &dm->serverOnNetwork, pointers, son_tmp) { - LIST_REMOVE(son, pointers); - UA_ServerOnNetwork_clear(&son->serverOnNetwork); - if(son->pathTmp) - UA_free(son->pathTmp); - UA_free(son); - } - - UA_String_clear(&dm->selfFqdnMdnsRecord); - - for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { - serverOnNetwork_hash_entry* currHash = dm->serverOnNetworkHash[i]; - while(currHash) { - serverOnNetwork_hash_entry* nextHash = currHash->next; - UA_free(currHash); - currHash = nextHash; - } - } - - /* Clean up mdns daemon */ - if(dm->mdnsDaemon) { - mdnsd_shutdown(dm->mdnsDaemon); - mdnsd_free(dm->mdnsDaemon); - dm->mdnsDaemon = NULL; - } + UA_DiscoveryManager_clearMdns(dm); # endif /* UA_ENABLE_DISCOVERY_MULTICAST */ return UA_STATUSCODE_GOOD; @@ -154,8 +129,7 @@ UA_DiscoveryManager_cleanupTimedOut(UA_Server *server, void *data) { dm->registeredServersSize--; } } - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD /* Send out multicast */ UA_DiscoveryManager_sendMulticastMessages(dm); #endif @@ -172,8 +146,7 @@ UA_DiscoveryManager_start(struct UA_ServerComponent *sc, UA_DiscoveryManager *dm = (UA_DiscoveryManager*)sc; #ifdef UA_ENABLE_DISCOVERY_MULTICAST - UA_EventLoop *el = server->config.eventLoop; - dm->serverOnNetworkRecordIdLastReset = el->dateTime_now(el); + UA_DiscoveryManager_resetServerOnNetworkRecordCounter(dm); #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ UA_StatusCode res = diff --git a/src/server/ua_discovery.h b/src/server/ua_discovery.h index 8c8fc2c66..852e0aabf 100644 --- a/src/server/ua_discovery.h +++ b/src/server/ua_discovery.h @@ -47,38 +47,6 @@ typedef struct { } asyncRegisterRequest; #define UA_MAXREGISTERREQUESTS 4 -#ifdef UA_ENABLE_DISCOVERY_MULTICAST - -#include "mdnsd/libmdnsd/mdnsd.h" -#define UA_MAXMDNSRECVSOCKETS 8 - -/** - * TXT record: - * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... - * - * A/AAAA record for all ip addresses: - * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. - * [hostname]. A [ip]. - */ - -typedef struct serverOnNetwork { - LIST_ENTRY(serverOnNetwork) pointers; - UA_ServerOnNetwork serverOnNetwork; - UA_DateTime created; - UA_DateTime lastSeen; - UA_Boolean txtSet; - UA_Boolean srvSet; - char* pathTmp; -} serverOnNetwork; - -#define SERVER_ON_NETWORK_HASH_SIZE 1000 -typedef struct serverOnNetwork_hash_entry { - serverOnNetwork *entry; - struct serverOnNetwork_hash_entry* next; -} serverOnNetwork_hash_entry; - -#endif - struct UA_DiscoveryManager { UA_ServerComponent sc; @@ -93,29 +61,13 @@ struct UA_DiscoveryManager { void* registerServerCallbackData; # ifdef UA_ENABLE_DISCOVERY_MULTICAST - mdns_daemon_t *mdnsDaemon; - UA_ConnectionManager *cm; - uintptr_t mdnsSendConnection; - uintptr_t mdnsRecvConnections[UA_MAXMDNSRECVSOCKETS]; - size_t mdnsRecvConnectionsSize; UA_Boolean mdnsMainSrvAdded; - - /* Full Domain Name of server itself. Used to detect if received mDNS - * message was from itself */ - UA_String selfFqdnMdnsRecord; - - LIST_HEAD(, serverOnNetwork) serverOnNetwork; - - UA_UInt32 serverOnNetworkRecordIdCounter; - UA_DateTime serverOnNetworkRecordIdLastReset; - - /* hash mapping domain name to serverOnNetwork list entry */ - struct serverOnNetwork_hash_entry* serverOnNetworkHash[SERVER_ON_NETWORK_HASH_SIZE]; - UA_Server_serverOnNetworkCallback serverOnNetworkCallback; void *serverOnNetworkCallbackData; - UA_UInt64 mdnsCallbackId; +# ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD + UA_ConnectionManager *cm; +# endif # endif /* UA_ENABLE_DISCOVERY_MULTICAST */ }; @@ -141,32 +93,34 @@ UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm); void UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm); +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD void UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm); +#endif + +void +UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm); + +UA_UInt32 +UA_DiscoveryManager_getMdnsConnectionCount(void); + +UA_UInt32 +UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(UA_DiscoveryManager *dm); UA_StatusCode -UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, - UA_String serverName, - struct serverOnNetwork **addedEntry); +UA_DiscoveryManager_resetServerOnNetworkRecordCounter(UA_DiscoveryManager *dm); + +UA_DateTime +UA_DiscoveryManager_getServerOnNetworkCounterResetTime(UA_DiscoveryManager *dm); + +UA_ServerOnNetwork* +UA_DiscoveryManager_getServerOnNetworkList(UA_DiscoveryManager *dm); + +UA_ServerOnNetwork* +UA_DiscoveryManager_getNextServerOnNetworkRecord(UA_DiscoveryManager *dm, + UA_ServerOnNetwork *current); UA_StatusCode -UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, - UA_String serverName); - -void mdns_record_received(const struct resource *r, void *data); - -void mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *path, const UA_String *capabilites, - const size_t capabilitiesSize, - void (*conflict)(char *host, int type, void *arg)); - -void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain); - -mdns_record_t * -mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, - const char *host, const char *rdname); +UA_DiscoveryManager_clearServerOnNetwork(UA_DiscoveryManager *dm); #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ diff --git a/src/server/ua_discovery_mdns.c b/src/server/ua_discovery_mdns.c index 37acc890e..019e1c3fb 100644 --- a/src/server/ua_discovery_mdns.c +++ b/src/server/ua_discovery_mdns.c @@ -9,8 +9,8 @@ #include "ua_discovery.h" #include "ua_server_internal.h" - -#ifdef UA_ENABLE_DISCOVERY_MULTICAST +#include "mdnsd/libmdnsd/mdnsd.h" +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD #ifndef UA_ENABLE_AMALGAMATION #include "mdnsd/libmdnsd/xht.h" @@ -37,12 +37,55 @@ # include // for recvfrom in cygwin #endif +#define UA_MAXMDNSRECVSOCKETS 8 +#define SERVER_ON_NETWORK_HASH_SIZE 1000 +typedef struct serverOnNetwork { + LIST_ENTRY(serverOnNetwork) pointers; + UA_ServerOnNetwork serverOnNetwork; + UA_DateTime created; + UA_DateTime lastSeen; + UA_Boolean txtSet; + UA_Boolean srvSet; + char* pathTmp; +} serverOnNetwork; + +typedef struct serverOnNetwork_hash_entry { + serverOnNetwork *entry; + struct serverOnNetwork_hash_entry* next; +} serverOnNetwork_hash_entry; + +typedef struct mdnsPrivate { + mdns_daemon_t *mdnsDaemon; + uintptr_t mdnsSendConnection; + uintptr_t mdnsRecvConnections[UA_MAXMDNSRECVSOCKETS]; + size_t mdnsRecvConnectionsSize; + /* hash mapping domain name to serverOnNetwork list entry */ + struct serverOnNetwork_hash_entry* serverOnNetworkHash[SERVER_ON_NETWORK_HASH_SIZE]; + LIST_HEAD(, serverOnNetwork) serverOnNetwork; + /* Name of server itself. Used to detect if received mDNS + * message was from itself */ + UA_String selfMdnsRecord; + + UA_UInt32 serverOnNetworkRecordIdCounter; + UA_DateTime serverOnNetworkRecordIdLastReset; +} mdnsPrivate; + + +static UA_StatusCode +UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + UA_String serverName, + struct serverOnNetwork **addedEntry); + + +static mdnsPrivate mdnsPrivateData; + static struct serverOnNetwork * mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, UA_String serverName, UA_Boolean createNew) { UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; while(hash_entry) { size_t maxLen = serverName.length; @@ -68,7 +111,7 @@ mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, } -UA_StatusCode +static UA_StatusCode UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, const char *fqdnMdnsRecord, UA_String serverName, @@ -97,15 +140,15 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, listEntry->txtSet = false; listEntry->srvSet = false; UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); - listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; + listEntry->serverOnNetwork.recordId = mdnsPrivateData.serverOnNetworkRecordIdCounter; UA_StatusCode res = UA_String_copy(&serverName, &listEntry->serverOnNetwork.serverName); if(res != UA_STATUSCODE_GOOD) { UA_free(listEntry); return res; } - dm->serverOnNetworkRecordIdCounter++; - if(dm->serverOnNetworkRecordIdCounter == 0) - dm->serverOnNetworkRecordIdLastReset = el->dateTime_now(el); + mdnsPrivateData.serverOnNetworkRecordIdCounter++; + if(mdnsPrivateData.serverOnNetworkRecordIdCounter == 0) + mdnsPrivateData.serverOnNetworkRecordIdLastReset = el->dateTime_now(el); listEntry->lastSeen = el->dateTime_nowMonotonic(el); /* add to hash */ @@ -118,11 +161,11 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, UA_free(listEntry); return UA_STATUSCODE_BADOUTOFMEMORY; } - newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; - dm->serverOnNetworkHash[hashIdx] = newHashEntry; + newHashEntry->next = mdnsPrivateData.serverOnNetworkHash[hashIdx]; + mdnsPrivateData.serverOnNetworkHash[hashIdx] = newHashEntry; newHashEntry->entry = listEntry; - LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); + LIST_INSERT_HEAD(&mdnsPrivateData.serverOnNetwork, listEntry, pointers); if(addedEntry != NULL) *addedEntry = listEntry; @@ -177,7 +220,7 @@ getInterfaces(UA_DiscoveryManager *dm) { #endif /* _WIN32 */ -UA_StatusCode +static UA_StatusCode UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, const char *fqdnMdnsRecord, UA_String serverName) { @@ -199,12 +242,12 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, /* remove from hash */ UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; struct serverOnNetwork_hash_entry *prevEntry = hash_entry; while(hash_entry) { if(hash_entry->entry == entry) { - if(dm->serverOnNetworkHash[hashIdx] == hash_entry) - dm->serverOnNetworkHash[hashIdx] = hash_entry->next; + if(mdnsPrivateData.serverOnNetworkHash[hashIdx] == hash_entry) + mdnsPrivateData.serverOnNetworkHash[hashIdx] = hash_entry->next; else if(prevEntry) prevEntry->next = hash_entry->next; break; @@ -215,7 +258,7 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, UA_free(hash_entry); if(dm->serverOnNetworkCallback && - !UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr)) + !UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &recordStr)) dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, entry->txtSet, dm->serverOnNetworkCallbackData); @@ -342,7 +385,7 @@ setSrv(UA_DiscoveryManager *dm, const struct resource *r, } /* This will be called by the mDNS library on every record which is received */ -void +static void mdns_record_received(const struct resource *r, void *data) { UA_DiscoveryManager *dm = (UA_DiscoveryManager*) data; @@ -360,7 +403,7 @@ mdns_record_received(const struct resource *r, void *data) { UA_String recordStr; recordStr.data = (UA_Byte*)r->name; recordStr.length = strlen(r->name); - UA_Boolean isSelfAnnounce = UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr); + UA_Boolean isSelfAnnounce = UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &recordStr); if(isSelfAnnounce) return; // ignore itself @@ -412,11 +455,11 @@ mdns_record_received(const struct resource *r, void *data) { dm->serverOnNetworkCallbackData); } -void +static void mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *path, const UA_String *capabilites, const size_t capabilitiesSize, void (*conflict)(char *host, int type, void *arg)) { - mdns_record_t *r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + mdns_record_t *r = mdnsd_unique(mdnsPrivateData.mdnsDaemon, fullServiceDomain, QTYPE_TXT, 600, conflict, dm); xht_t *h = xht_new(11); char *allocPath = NULL; @@ -480,12 +523,12 @@ mdns_create_txt(UA_DiscoveryManager *dm, const char *fullServiceDomain, const ch if(caps) UA_free(caps); xht_free(h); - mdnsd_set_raw(dm->mdnsDaemon, r, (char *) packet, + mdnsd_set_raw(mdnsPrivateData.mdnsDaemon, r, (char *) packet, (unsigned short) txtRecordLength); UA_free(packet); } -mdns_record_t * +static mdns_record_t * mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type, const char *host, const char *rdname) { mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host); @@ -507,18 +550,18 @@ static void mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *localDomain, char *addr, UA_UInt16 addr_len) { /* [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. */ - mdns_record_t *r = mdnsd_shared(dm->mdnsDaemon, fullServiceDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); + mdns_record_t *r = mdnsd_shared(mdnsPrivateData.mdnsDaemon, fullServiceDomain, QTYPE_A, 600); + mdnsd_set_raw(mdnsPrivateData.mdnsDaemon, r, addr, addr_len); /* [hostname]. A [ip]. */ - r = mdnsd_shared(dm->mdnsDaemon, localDomain, QTYPE_A, 600); - mdnsd_set_raw(dm->mdnsDaemon, r, addr, addr_len); + r = mdnsd_shared(mdnsPrivateData.mdnsDaemon, localDomain, QTYPE_A, 600); + mdnsd_set_raw(mdnsPrivateData.mdnsDaemon, r, addr, addr_len); } /* Loop over network interfaces and run set_address_record on each */ #ifdef _WIN32 - -void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, +static void +mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *localDomain) { IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(dm); if(!adapter_addresses) @@ -575,7 +618,7 @@ void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDom #elif defined(UA_HAS_GETIFADDR) -void +static void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, const char *localDomain) { struct ifaddrs *ifaddr; @@ -629,6 +672,90 @@ mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, #endif /* _WIN32 */ +UA_StatusCode +UA_DiscoveryManager_clearServerOnNetwork(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return UA_STATUSCODE_BADINVALIDARGUMENT; + } + + serverOnNetwork *son, *son_tmp; + LIST_FOREACH_SAFE(son, &mdnsPrivateData.serverOnNetwork, pointers, son_tmp) { + LIST_REMOVE(son, pointers); + UA_ServerOnNetwork_clear(&son->serverOnNetwork); + if(son->pathTmp) + UA_free(son->pathTmp); + UA_free(son); + } + + UA_String_clear(&mdnsPrivateData.selfMdnsRecord); + + for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { + serverOnNetwork_hash_entry* currHash = mdnsPrivateData.serverOnNetworkHash[i]; + while(currHash) { + serverOnNetwork_hash_entry* nextHash = currHash->next; + UA_free(currHash); + currHash = nextHash; + } + } + + return UA_STATUSCODE_GOOD; +} + +UA_ServerOnNetwork* +UA_DiscoveryManager_getServerOnNetworkList(UA_DiscoveryManager *dm) { + serverOnNetwork* entry = LIST_FIRST(&mdnsPrivateData.serverOnNetwork); + return entry ? &entry->serverOnNetwork : NULL; +} + +UA_ServerOnNetwork* +UA_DiscoveryManager_getNextServerOnNetworkRecord(UA_DiscoveryManager *dm, + UA_ServerOnNetwork *current) { + serverOnNetwork *entry = NULL; + LIST_FOREACH(entry, &mdnsPrivateData.serverOnNetwork, pointers) { + if(&entry->serverOnNetwork == current) { + entry = LIST_NEXT(entry, pointers); + break; + } + } + return entry ? &entry->serverOnNetwork : NULL; +} + + +UA_UInt32 +UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return 0; + } + return mdnsPrivateData.serverOnNetworkRecordIdCounter; +} + +UA_StatusCode +UA_DiscoveryManager_resetServerOnNetworkRecordCounter(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return UA_STATUSCODE_BADINTERNALERROR; + } + mdnsPrivateData.serverOnNetworkRecordIdCounter = 0; + mdnsPrivateData.serverOnNetworkRecordIdLastReset = dm->sc.server->config.eventLoop->dateTime_now( + dm->sc.server->config.eventLoop); + return UA_STATUSCODE_GOOD; +} + +UA_DateTime +UA_DiscoveryManager_getServerOnNetworkCounterResetTime(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return 0; + } + return mdnsPrivateData.serverOnNetworkRecordIdLastReset; +} + typedef enum { UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ UA_DISCOVERY_TLS /* OPC UA HTTPS mapping */ @@ -666,19 +793,19 @@ static void mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, UA_Boolean recv) { if(!recv) { - dm->mdnsSendConnection = connectionId; + mdnsPrivateData.mdnsSendConnection = connectionId; return; } for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] == connectionId) + if(mdnsPrivateData.mdnsRecvConnections[i] == connectionId) return; } for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != 0) + if(mdnsPrivateData.mdnsRecvConnections[i] != 0) continue; - dm->mdnsRecvConnections[i] = connectionId; - dm->mdnsRecvConnectionsSize++; + mdnsPrivateData.mdnsRecvConnections[i] = connectionId; + mdnsPrivateData.mdnsRecvConnectionsSize++; break; } } @@ -686,15 +813,15 @@ mdnsAddConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, static void mdnsRemoveConnection(UA_DiscoveryManager *dm, uintptr_t connectionId, UA_Boolean recv) { - if(dm->mdnsSendConnection == connectionId) { - dm->mdnsSendConnection = 0; + if(mdnsPrivateData.mdnsSendConnection == connectionId) { + mdnsPrivateData.mdnsSendConnection = 0; return; } for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) { - if(dm->mdnsRecvConnections[i] != connectionId) + if(mdnsPrivateData.mdnsRecvConnections[i] != connectionId) continue; - dm->mdnsRecvConnections[i] = 0; - dm->mdnsRecvConnectionsSize--; + mdnsPrivateData.mdnsRecvConnections[i] = 0; + mdnsPrivateData.mdnsRecvConnectionsSize--; break; } } @@ -756,7 +883,7 @@ MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, memset(&mm, 0, sizeof(struct message)); UA_Boolean rr = message_parse(&mm, (unsigned char*)msg.data, msg.length); if(rr) - mdnsd_in(dm->mdnsDaemon, &mm, infoptr->ai_addr, + mdnsd_in(mdnsPrivateData.mdnsDaemon, &mm, infoptr->ai_addr, (unsigned short)infoptr->ai_addrlen); freeaddrinfo(infoptr); } @@ -764,7 +891,7 @@ MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { UA_ConnectionManager *cm = dm->cm; - if(!dm->cm || dm->mdnsSendConnection == 0) + if(!dm->cm || mdnsPrivateData.mdnsSendConnection == 0) return; struct sockaddr ip; @@ -775,18 +902,18 @@ UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { memset(&mm, 0, sizeof(struct message)); unsigned short sport = 0; - while(mdnsd_out(dm->mdnsDaemon, &mm, &ip, &sport) > 0) { + while(mdnsd_out(mdnsPrivateData.mdnsDaemon, &mm, &ip, &sport) > 0) { int len = message_packet_len(&mm); char* buf = (char*)message_packet(&mm); if(len <= 0) continue; UA_ByteString sendBuf = UA_BYTESTRING_NULL; - UA_StatusCode rv = cm->allocNetworkBuffer(cm, dm->mdnsSendConnection, + UA_StatusCode rv = cm->allocNetworkBuffer(cm, mdnsPrivateData.mdnsSendConnection, &sendBuf, (size_t)len); if(rv != UA_STATUSCODE_GOOD) continue; memcpy(sendBuf.data, buf, sendBuf.length); - cm->sendWithConnection(cm, dm->mdnsSendConnection, + cm->sendWithConnection(cm, mdnsPrivateData.mdnsSendConnection, &UA_KEYVALUEMAP_NULL, &sendBuf); } } @@ -901,7 +1028,7 @@ discovery_createMulticastSocket(UA_DiscoveryManager *dm) { UA_KeyValueMap kvm = {paramsSize, params}; UA_StatusCode res = UA_STATUSCODE_GOOD; - if(dm->mdnsRecvConnectionsSize == 0) { + if(mdnsPrivateData.mdnsRecvConnectionsSize == 0) { res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, MulticastDiscoveryRecvCallback); if(res != UA_STATUSCODE_GOOD) @@ -911,7 +1038,7 @@ discovery_createMulticastSocket(UA_DiscoveryManager *dm) { /* Open the send connection */ listen = false; - if(dm->mdnsSendConnection == 0) { + if(mdnsPrivateData.mdnsSendConnection == 0) { res = dm->cm->openConnection(dm->cm, &kvm, dm->sc.server, dm, MulticastDiscoverySendCallback); if(res != UA_STATUSCODE_GOOD) @@ -922,9 +1049,9 @@ discovery_createMulticastSocket(UA_DiscoveryManager *dm) { void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { - if(!dm->mdnsDaemon) { - dm->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); - mdnsd_register_receive_callback(dm->mdnsDaemon, mdns_record_received, dm); + if(!mdnsPrivateData.mdnsDaemon) { + mdnsPrivateData.mdnsDaemon = mdnsd_new(QCLASS_IN, 1000); + mdnsd_register_receive_callback(mdnsPrivateData.mdnsDaemon, mdns_record_received, dm); } #if defined(UA_ARCHITECTURE_WIN32) || defined(UA_ARCHITECTURE_WEC7) @@ -933,9 +1060,9 @@ UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { #endif /* Open the mdns listen socket */ - if(dm->mdnsSendConnection == 0) + if(mdnsPrivateData.mdnsSendConnection == 0) discovery_createMulticastSocket(dm); - if(dm->mdnsSendConnection == 0) { + if(mdnsPrivateData.mdnsSendConnection == 0) { UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Could not create multicast socket"); return; @@ -948,7 +1075,7 @@ UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { /* Send a multicast probe to find any other OPC UA server on the network * through mDNS */ - mdnsd_query(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + mdnsd_query(mdnsPrivateData.mdnsDaemon, "_opcua-tcp._tcp.local.", QTYPE_PTR,discovery_multicastQueryAnswer, dm->sc.server); } @@ -982,14 +1109,50 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { /* Close the socket */ if(dm->cm) { - if(dm->mdnsSendConnection) - dm->cm->closeConnection(dm->cm, dm->mdnsSendConnection); + if(mdnsPrivateData.mdnsSendConnection) + dm->cm->closeConnection(dm->cm, mdnsPrivateData.mdnsSendConnection); for(size_t i = 0; i < UA_MAXMDNSRECVSOCKETS; i++) - if(dm->mdnsRecvConnections[i] != 0) - dm->cm->closeConnection(dm->cm, dm->mdnsRecvConnections[i]); + if(mdnsPrivateData.mdnsRecvConnections[i] != 0) + dm->cm->closeConnection(dm->cm, mdnsPrivateData.mdnsRecvConnections[i]); } } +void +UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm) { + /* Clean up the serverOnNetwork list */ + serverOnNetwork *son, *son_tmp; + LIST_FOREACH_SAFE(son, &mdnsPrivateData.serverOnNetwork, pointers, son_tmp) { + LIST_REMOVE(son, pointers); + UA_ServerOnNetwork_clear(&son->serverOnNetwork); + if(son->pathTmp) + UA_free(son->pathTmp); + UA_free(son); + } + + UA_String_clear(&mdnsPrivateData.selfMdnsRecord); + + for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { + serverOnNetwork_hash_entry* currHash = mdnsPrivateData.serverOnNetworkHash[i]; + while(currHash) { + serverOnNetwork_hash_entry* nextHash = currHash->next; + UA_free(currHash); + currHash = nextHash; + } + } + + /* Clean up mdns daemon */ + if(mdnsPrivateData.mdnsDaemon) { + mdnsd_shutdown(mdnsPrivateData.mdnsDaemon); + mdnsd_free(mdnsPrivateData.mdnsDaemon); + mdnsPrivateData.mdnsDaemon = NULL; + } +} + +UA_UInt32 +UA_DiscoveryManager_getMdnsConnectionCount(void) { + return mdnsPrivateData.mdnsRecvConnectionsSize + (mdnsPrivateData.mdnsSendConnection != 0); +} + void UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String serverName, const UA_MdnsDiscoveryConfiguration *mdnsConfig, @@ -1096,7 +1259,7 @@ static UA_Boolean UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, unsigned short port, const UA_DiscoveryProtocol protocol) { // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. - mdns_record_t *r = mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + mdns_record_t *r = mdnsd_get_published(mdnsPrivateData.mdnsDaemon, fullServiceDomain); while(r) { const mdns_answer_t *data = mdnsd_record_data(r); if(data->type == QTYPE_SRV && (port == 0 || data->srv.port == port)) @@ -1126,15 +1289,15 @@ discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) { if(exists == true) return 0; - if(mdnsd_has_query(dm->mdnsDaemon, a->rdname)) + if(mdnsd_has_query(mdnsPrivateData.mdnsDaemon, a->rdname)) return 0; UA_LOG_DEBUG(server->config.logging, UA_LOGCATEGORY_DISCOVERY, "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname); - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_SRV, + mdnsd_query(mdnsPrivateData.mdnsDaemon, a->rdname, QTYPE_SRV, discovery_multicastQueryAnswer, server); - mdnsd_query(dm->mdnsDaemon, a->rdname, QTYPE_TXT, + mdnsd_query(mdnsPrivateData.mdnsDaemon, a->rdname, QTYPE_TXT, discovery_multicastQueryAnswer, server); return 0; } @@ -1170,9 +1333,9 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, if(!dm->mdnsMainSrvAdded) { mdns_record_t *r = - mdnsd_shared(dm->mdnsDaemon, "_services._dns-sd._udp.local.", + mdnsd_shared(mdnsPrivateData.mdnsDaemon, "_services._dns-sd._udp.local.", QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, "_opcua-tcp._tcp.local."); + mdnsd_set_host(mdnsPrivateData.mdnsDaemon, r, "_opcua-tcp._tcp.local."); dm->mdnsMainSrvAdded = true; } @@ -1188,9 +1351,9 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Multicast DNS: add record for domain: %s", fullServiceDomain); - if(isSelf && dm->selfFqdnMdnsRecord.length == 0) { - dm->selfFqdnMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); - if(!dm->selfFqdnMdnsRecord.data) + if(isSelf && mdnsPrivateData.selfMdnsRecord.length == 0) { + mdnsPrivateData.selfMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); + if(!mdnsPrivateData.selfMdnsRecord.data) return UA_STATUSCODE_BADOUTOFMEMORY; } @@ -1248,12 +1411,12 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + mdns_find_record(mdnsPrivateData.mdnsDaemon, QTYPE_PTR, "_opcua-tcp._tcp.local.", fullServiceDomain); if(!r) { - r = mdnsd_shared(dm->mdnsDaemon, "_opcua-tcp._tcp.local.", + r = mdnsd_shared(mdnsPrivateData.mdnsDaemon, "_opcua-tcp._tcp.local.", QTYPE_PTR, 600); - mdnsd_set_host(dm->mdnsDaemon, r, fullServiceDomain); + mdnsd_set_host(mdnsPrivateData.mdnsDaemon, r, fullServiceDomain); } /* The first 63 characters of the hostname (or less) */ @@ -1264,9 +1427,9 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, localDomain[maxHostnameLen+1] = '\0'; /* [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname]. */ - r = mdnsd_unique(dm->mdnsDaemon, fullServiceDomain, + r = mdnsd_unique(mdnsPrivateData.mdnsDaemon, fullServiceDomain, QTYPE_SRV, 600, UA_Discovery_multicastConflict, dm); - mdnsd_set_srv(dm->mdnsDaemon, r, 0, 0, port, localDomain); + mdnsd_set_srv(mdnsPrivateData.mdnsDaemon, r, 0, 0, port, localDomain); /* A/AAAA record for all ip addresses. * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. @@ -1319,7 +1482,7 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, /* _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local. */ mdns_record_t *r = - mdns_find_record(dm->mdnsDaemon, QTYPE_PTR, + mdns_find_record(mdnsPrivateData.mdnsDaemon, QTYPE_PTR, "_opcua-tcp._tcp.local.", fullServiceDomain); if(!r) { UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, @@ -1327,14 +1490,14 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, "PTR Record not found for domain: %s", fullServiceDomain); return UA_STATUSCODE_BADNOTHINGTODO; } - mdnsd_done(dm->mdnsDaemon, r); + mdnsd_done(mdnsPrivateData.mdnsDaemon, r); /* looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 * port hostname.local. and TXT record: * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... * and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip] */ mdns_record_t *r2 = - mdnsd_get_published(dm->mdnsDaemon, fullServiceDomain); + mdnsd_get_published(mdnsPrivateData.mdnsDaemon, fullServiceDomain); if(!r2) { UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Multicast DNS: could not remove record. Record not " @@ -1348,7 +1511,7 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, if((removeTxt && data->type == QTYPE_TXT) || (removeTxt && data->type == QTYPE_A) || data->srv.port == port) { - mdnsd_done(dm->mdnsDaemon, r2); + mdnsd_done(mdnsPrivateData.mdnsDaemon, r2); } r2 = next; } diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index 6b37f7d8c..a05ea04ca 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -107,55 +107,7 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, return UA_STATUSCODE_GOOD; } -#ifdef _WIN32 - -/* see http://stackoverflow.com/a/10838854/869402 */ -static IP_ADAPTER_ADDRESSES * -getInterfaces(UA_DiscoveryManager *dm) { - IP_ADAPTER_ADDRESSES* adapter_addresses = NULL; - - /* Start with a 16 KB buffer and resize if needed - multiple attempts in - * case interfaces change while we are in the middle of querying them. */ - DWORD adapter_addresses_buffer_size = 16 * 1024; - for(size_t attempts = 0; attempts != 3; ++attempts) { - /* todo: malloc may fail: return a statuscode */ - adapter_addresses = (IP_ADAPTER_ADDRESSES*)UA_malloc(adapter_addresses_buffer_size); - if(!adapter_addresses) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "GetAdaptersAddresses out of memory"); - adapter_addresses = NULL; - break; - } - DWORD error = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_ANYCAST | - GAA_FLAG_SKIP_DNS_SERVER | - GAA_FLAG_SKIP_FRIENDLY_NAME, - NULL, adapter_addresses, - &adapter_addresses_buffer_size); - - if(ERROR_SUCCESS == error) { - break; - } else if (ERROR_BUFFER_OVERFLOW == error) { - /* Try again with the new size */ - UA_free(adapter_addresses); - adapter_addresses = NULL; - continue; - } - - /* Unexpected error */ - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "GetAdaptersAddresses returned an unexpected error. " - "Not setting mDNS A records."); - UA_free(adapter_addresses); - adapter_addresses = NULL; - break; - } - return adapter_addresses; -} - -#endif /* _WIN32 */ - -UA_StatusCode +static UA_StatusCode UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, const char *fqdnMdnsRecord, UA_String serverName) { @@ -222,127 +174,6 @@ mdns_append_path_to_url(UA_String *url, const char *path) { url->data = (UA_Byte *) newUrl; } -/* set record in the given interface */ -static void -mdns_set_address_record_if(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain, char *addr, UA_UInt16 addr_len) { - -} - -/* Loop over network interfaces and run set_address_record on each */ -#ifdef _WIN32 - -void mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(dm); - if(!adapter_addresses) - return; - - /* Iterate through all of the adapters */ - IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; - for(; adapter != NULL; adapter = adapter->Next) { - /* Skip loopback adapters */ - if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) - continue; - - /* Parse all IPv4 and IPv6 addresses */ - IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress; - for(; NULL != address; address = address->Next) { - int family = address->Address.lpSockaddr->sa_family; - if(AF_INET == family) { - SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); /* IPv4 */ - mdns_set_address_record_if(dm, fullServiceDomain, - localDomain, (char *)&ipv4->sin_addr, 4); - } else if(AF_INET6 == family) { - /* IPv6 */ -#if 0 - SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr); - - char str_buffer[INET6_ADDRSTRLEN] = {0}; - inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN); - - std::string ipv6_str(str_buffer); - - /* Detect and skip non-external addresses */ - UA_Boolean is_link_local(false); - UA_Boolean is_special_use(false); - - if(0 == ipv6_str.find("fe")) { - char c = ipv6_str[2]; - if(c == '8' || c == '9' || c == 'a' || c == 'b') - is_link_local = true; - } else if (0 == ipv6_str.find("2001:0:")) { - is_special_use = true; - } - - if(!(is_link_local || is_special_use)) - ipAddrs.mIpv6.push_back(ipv6_str); -#endif - } - } - } - - /* Cleanup */ - UA_free(adapter_addresses); - adapter_addresses = NULL; -} - -#elif defined(UA_HAS_GETIFADDR) - -void -mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - struct ifaddrs *ifaddr; - struct ifaddrs *ifa; - if(getifaddrs(&ifaddr) == -1) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "getifaddrs returned an unexpected error. Not setting mDNS A records."); - return; - } - - /* Walk through linked list, maintaining head pointer so we can free list later */ - int n; - for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { - if(!ifa->ifa_addr) - continue; - - if((strcmp("lo", ifa->ifa_name) == 0) || - !(ifa->ifa_flags & (IFF_RUNNING))|| - !(ifa->ifa_flags & (IFF_MULTICAST))) - continue; - - /* IPv4 */ - if(ifa->ifa_addr->sa_family == AF_INET) { - struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; - mdns_set_address_record_if(dm, fullServiceDomain, - localDomain, (char*)&sa->sin_addr.s_addr, 4); - } - - /* IPv6 not implemented yet */ - } - - /* Clean up */ - freeifaddrs(ifaddr); -} -#else /* _WIN32 */ - -void -mdns_set_address_record(UA_DiscoveryManager *dm, const char *fullServiceDomain, - const char *localDomain) { - if(dm->sc.server->config.mdnsIpAddressListSize == 0) { - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "If UA_HAS_GETIFADDR is false, config.mdnsIpAddressList must be set"); - return; - } - - for(size_t i=0; i< dm->sc.server->config.mdnsIpAddressListSize; i++) { - mdns_set_address_record_if(dm, fullServiceDomain, localDomain, - (char*)&dm->sc.server->config.mdnsIpAddressList[i], 4); - } -} - -#endif /* _WIN32 */ - typedef enum { UA_DISCOVERY_TCP, /* OPC UA TCP mapping */ UA_DISCOVERY_TLS /* OPC UA HTTPS mapping */ @@ -448,6 +279,30 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { } } +void +UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm) { + /* Clean up the serverOnNetwork list */ + serverOnNetwork *son, *son_tmp; + LIST_FOREACH_SAFE(son, &dm->serverOnNetwork, pointers, son_tmp) { + LIST_REMOVE(son, pointers); + UA_ServerOnNetwork_clear(&son->serverOnNetwork); + if(son->pathTmp) + UA_free(son->pathTmp); + UA_free(son); + } + + UA_String_clear(&dm->selfFqdnMdnsRecord); + + for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { + serverOnNetwork_hash_entry* currHash = dm->serverOnNetworkHash[i]; + while(currHash) { + serverOnNetwork_hash_entry* nextHash = currHash->next; + UA_free(currHash); + currHash = nextHash; + } + } +} + void UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String serverName, const UA_MdnsDiscoveryConfiguration *mdnsConfig, @@ -665,10 +520,6 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, localDomain[maxHostnameLen+1] = '\0'; - /* A/AAAA record for all ip addresses. - * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]. - * [hostname]. A [ip]. */ - mdns_set_address_record(dm, fullServiceDomain, localDomain); /* TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ UA_STACKARRAY(char, pathChars, path.length + 1); diff --git a/src/server/ua_server_config.c b/src/server/ua_server_config.c index 5147091f4..db3813b19 100644 --- a/src/server/ua_server_config.c +++ b/src/server/ua_server_config.c @@ -20,12 +20,14 @@ UA_ServerConfig_clear(UA_ServerConfig *config) { UA_ApplicationDescription_clear(&config->applicationDescription); #ifdef UA_ENABLE_DISCOVERY_MULTICAST UA_MdnsDiscoveryConfiguration_clear(&config->mdnsConfig); +#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD UA_String_clear(&config->mdnsInterfaceIP); # if !defined(UA_HAS_GETIFADDR) if (config->mdnsIpAddressListSize) { UA_free(config->mdnsIpAddressList); } # endif +#endif #endif /* Stop and delete the EventLoop */ diff --git a/src/server/ua_services_discovery.c b/src/server/ua_services_discovery.c index 7116e81c0..ea50dbbe1 100644 --- a/src/server/ua_services_discovery.c +++ b/src/server/ua_services_discovery.c @@ -194,15 +194,15 @@ void Service_FindServers(UA_Server *server, UA_Session *session, static UA_Boolean entryMatchesCapabilityFilter(size_t serverCapabilityFilterSize, UA_String *serverCapabilityFilter, - serverOnNetwork *current) { + UA_ServerOnNetwork *current) { /* If the entry has less capabilities defined than the filter, there's no match */ - if(serverCapabilityFilterSize > current->serverOnNetwork.serverCapabilitiesSize) + if(serverCapabilityFilterSize > current->serverCapabilitiesSize) return false; for(size_t i = 0; i < serverCapabilityFilterSize; i++) { UA_Boolean capabilityFound = false; - for(size_t j = 0; j < current->serverOnNetwork.serverCapabilitiesSize; j++) { + for(size_t j = 0; j < current->serverCapabilitiesSize; j++) { if(UA_String_equal_ignorecase(&serverCapabilityFilter[i], - ¤t->serverOnNetwork.serverCapabilities[j])) { + ¤t->serverCapabilities[j])) { capabilityFound = true; break; } @@ -233,12 +233,14 @@ Service_FindServersOnNetwork(UA_Server *server, UA_Session *session, /* Set LastCounterResetTime */ response->lastCounterResetTime = - dm->serverOnNetworkRecordIdLastReset; + UA_DiscoveryManager_getServerOnNetworkCounterResetTime(dm); /* Compute the max number of records to return */ UA_UInt32 recordCount = 0; - if(request->startingRecordId < dm->serverOnNetworkRecordIdCounter) - recordCount = dm->serverOnNetworkRecordIdCounter - request->startingRecordId; + UA_UInt32 serverOnNetworkRecordIdCounter = + UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(dm); + if(request->startingRecordId < serverOnNetworkRecordIdCounter) + recordCount = serverOnNetworkRecordIdCounter - request->startingRecordId; if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn) recordCount = UA_MIN(recordCount, request->maxRecordsToReturn); if(recordCount == 0) { @@ -249,16 +251,21 @@ Service_FindServersOnNetwork(UA_Server *server, UA_Session *session, /* Iterate over all records and add to filtered list */ UA_UInt32 filteredCount = 0; UA_STACKARRAY(UA_ServerOnNetwork*, filtered, recordCount); - serverOnNetwork *current; - LIST_FOREACH(current, &dm->serverOnNetwork, pointers) { + UA_ServerOnNetwork *current = UA_DiscoveryManager_getServerOnNetworkList(dm); + if(!current) { + response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; + return; + } + for(size_t i = 0; i < recordCount; i++) { if(filteredCount >= recordCount) break; - if(current->serverOnNetwork.recordId < request->startingRecordId) + if(current->recordId < request->startingRecordId) continue; if(!entryMatchesCapabilityFilter(request->serverCapabilityFilterSize, request->serverCapabilityFilter, current)) continue; - filtered[filteredCount++] = ¤t->serverOnNetwork; + filtered[filteredCount++] = current; + current = UA_DiscoveryManager_getNextServerOnNetworkRecord(dm, current); } if(filteredCount == 0) From 0bc9e34288a442522874b96cdab17a81a42aa9d3 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Fri, 29 Nov 2024 14:58:01 +0100 Subject: [PATCH 085/158] fix(server): Clean up discovery timer The current function name is misleading because the timer is not only used for cleanup. Add additional functions to enable implementation-specific timer callback calls. Also discoveryCallbackId is already used to store the timer id for the discovery timer. Remove the incorrect mdnsCallbackId handling. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery.c | 12 ++++++++---- src/server/ua_discovery.h | 7 +++---- src/server/ua_discovery_mdns.c | 15 +++++---------- src/server/ua_discovery_mdns_avahi.c | 14 +++++--------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/server/ua_discovery.c b/src/server/ua_discovery.c index e6a3588c6..70c151707 100644 --- a/src/server/ua_discovery.c +++ b/src/server/ua_discovery.c @@ -129,9 +129,13 @@ UA_DiscoveryManager_cleanupTimedOut(UA_Server *server, void *data) { dm->registeredServersSize--; } } -#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD - /* Send out multicast */ - UA_DiscoveryManager_sendMulticastMessages(dm); +} + +static void +UA_DiscoveryManager_cyclicTimer(UA_Server *server, void *data) { + UA_DiscoveryManager_cleanupTimedOut(server, data); +#ifdef UA_ENABLE_DISCOVERY_MULTICAST + UA_DiscoveryManager_mdnsCyclicTimer(server, data); #endif } @@ -150,7 +154,7 @@ UA_DiscoveryManager_start(struct UA_ServerComponent *sc, #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ UA_StatusCode res = - addRepeatedCallback(server, UA_DiscoveryManager_cleanupTimedOut, + addRepeatedCallback(server, UA_DiscoveryManager_cyclicTimer, dm, 1000.0, &dm->discoveryCallbackId); if(res != UA_STATUSCODE_GOOD) return res; diff --git a/src/server/ua_discovery.h b/src/server/ua_discovery.h index 852e0aabf..8f7e9068a 100644 --- a/src/server/ua_discovery.h +++ b/src/server/ua_discovery.h @@ -64,7 +64,6 @@ struct UA_DiscoveryManager { UA_Boolean mdnsMainSrvAdded; UA_Server_serverOnNetworkCallback serverOnNetworkCallback; void *serverOnNetworkCallbackData; - UA_UInt64 mdnsCallbackId; # ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD UA_ConnectionManager *cm; # endif @@ -93,9 +92,6 @@ UA_Discovery_updateMdnsForDiscoveryUrl(UA_DiscoveryManager *dm, const UA_String void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm); void UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm); -#ifdef UA_ENABLE_DISCOVERY_MULTICAST_MDNSD -void UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm); -#endif void UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm); @@ -122,6 +118,9 @@ UA_DiscoveryManager_getNextServerOnNetworkRecord(UA_DiscoveryManager *dm, UA_StatusCode UA_DiscoveryManager_clearServerOnNetwork(UA_DiscoveryManager *dm); +void +UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data); + #endif /* UA_ENABLE_DISCOVERY_MULTICAST */ #endif /* UA_ENABLE_DISCOVERY */ diff --git a/src/server/ua_discovery_mdns.c b/src/server/ua_discovery_mdns.c index 019e1c3fb..de6e6e7d2 100644 --- a/src/server/ua_discovery_mdns.c +++ b/src/server/ua_discovery_mdns.c @@ -888,7 +888,7 @@ MulticastDiscoveryCallback(UA_ConnectionManager *cm, uintptr_t connectionId, freeaddrinfo(infoptr); } -void +static void UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { UA_ConnectionManager *cm = dm->cm; if(!dm->cm || mdnsPrivateData.mdnsSendConnection == 0) @@ -918,6 +918,10 @@ UA_DiscoveryManager_sendMulticastMessages(UA_DiscoveryManager *dm) { } } +void UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data) { + UA_DiscoveryManager_sendMulticastMessages((UA_DiscoveryManager*)data); +} + static void MulticastDiscoveryRecvCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void *application, void **connectionContext, @@ -1098,15 +1102,6 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { hostname, port, true); } - /* Stop the cyclic polling callback */ - if(dm->mdnsCallbackId != 0) { - UA_EventLoop *el = server->config.eventLoop; - if(el) { - el->removeTimer(el, dm->mdnsCallbackId); - dm->mdnsCallbackId = 0; - } - } - /* Close the socket */ if(dm->cm) { if(mdnsPrivateData.mdnsSendConnection) diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index a05ea04ca..f371e79b1 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -268,15 +268,6 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { UA_Discovery_removeRecord(dm, server->config.mdnsConfig.mdnsServerName, hostname, port, true); } - - /* Stop the cyclic polling callback */ - if(dm->mdnsCallbackId != 0) { - UA_EventLoop *el = server->config.eventLoop; - if(el) { - el->removeTimer(el, dm->mdnsCallbackId); - dm->mdnsCallbackId = 0; - } - } } void @@ -371,6 +362,11 @@ UA_Discovery_multicastConflict(char *name, int type, void *arg) { "'%s' for type %d", name, type); } +void +UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data) { + +} + /* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ static void createFullServiceDomain(char *outServiceDomain, size_t maxLen, From 28ee2c8f5c899356be5457483ca9dd38ac8b8218 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Fri, 29 Nov 2024 14:21:11 +0100 Subject: [PATCH 086/158] refactor(server): Implement RegisterServer using avahi Local discovery server should support registering OPC UA applications with RegisterServer/RegisterServer2 service. Multicast extension shall announce registered servers to the network using mDNS. Add this feature using avahi library. Create a new avahi entry group for every service including path and caps in the given UA_Discovery_addRecord function. Also rename fqdn variables and remove additional string size since a fully qualified name will be automatically generated. Like in the implementation based on libmdnsd, conflict with existing records will only be logged and record type is fixed to _opcua-tcp._tcp. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery_mdns_avahi.c | 473 ++++++++++++++++++++++----- 1 file changed, 393 insertions(+), 80 deletions(-) diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index f371e79b1..b794c6561 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -5,22 +5,80 @@ * Copyright 2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) * Copyright 2017 (c) Thomas Stalder, Blue Time Concept SA + * Copyright 2024 (c) Linutronix GmbH (Author: Vasilij Strassheim) */ #include "ua_discovery.h" #include "ua_server_internal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef UA_ENABLE_DISCOVERY_MULTICAST_AVAHI #include "../deps/mp_printf.h" +typedef struct serverOnNetwork { + LIST_ENTRY(serverOnNetwork) pointers; + UA_ServerOnNetwork serverOnNetwork; + AvahiEntryGroup *group; + UA_DateTime created; + UA_DateTime lastSeen; + UA_Boolean txtSet; + UA_Boolean srvSet; + char* pathTmp; +} serverOnNetwork; + +#define SERVER_ON_NETWORK_HASH_SIZE 1000 +typedef struct serverOnNetwork_hash_entry { + serverOnNetwork *entry; + struct serverOnNetwork_hash_entry* next; +} serverOnNetwork_hash_entry; + +typedef struct avahiPrivate { + AvahiClient *client; + AvahiSimplePoll *simple_poll; + UA_Server *server; + /* hash mapping domain name to serverOnNetwork list entry */ + struct serverOnNetwork_hash_entry* serverOnNetworkHash[SERVER_ON_NETWORK_HASH_SIZE]; + LIST_HEAD(, serverOnNetwork) serverOnNetwork; + /* Name of server itself. Used to detect if received mDNS + * message was from itself */ + UA_String selfMdnsRecord; + UA_UInt32 serverOnNetworkRecordIdCounter; + UA_DateTime serverOnNetworkRecordIdLastReset; +} avahiPrivate; + +static +avahiPrivate mdnsPrivateData; + +static UA_StatusCode +UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, + const char *fqdnMdnsRecord, + UA_String serverName, + struct serverOnNetwork **addedEntry); + +static void +UA_Discovery_multicastConflict(char *name, UA_DiscoveryManager *dm) { + /* In case logging is disabled */ + (void)name; + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Multicast DNS name conflict detected: '%s'", name); +} + static struct serverOnNetwork * mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, UA_String serverName, UA_Boolean createNew) { UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; while(hash_entry) { size_t maxLen = serverName.length; @@ -46,7 +104,7 @@ mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, } -UA_StatusCode +static UA_StatusCode UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, const char *fqdnMdnsRecord, UA_String serverName, @@ -75,15 +133,15 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, listEntry->txtSet = false; listEntry->srvSet = false; UA_ServerOnNetwork_init(&listEntry->serverOnNetwork); - listEntry->serverOnNetwork.recordId = dm->serverOnNetworkRecordIdCounter; + listEntry->serverOnNetwork.recordId = mdnsPrivateData.serverOnNetworkRecordIdCounter; UA_StatusCode res = UA_String_copy(&serverName, &listEntry->serverOnNetwork.serverName); if(res != UA_STATUSCODE_GOOD) { UA_free(listEntry); return res; } - dm->serverOnNetworkRecordIdCounter++; - if(dm->serverOnNetworkRecordIdCounter == 0) - dm->serverOnNetworkRecordIdLastReset = el->dateTime_now(el); + mdnsPrivateData.serverOnNetworkRecordIdCounter++; + if(mdnsPrivateData.serverOnNetworkRecordIdCounter == 0) + UA_DiscoveryManager_resetServerOnNetworkRecordCounter(dm); listEntry->lastSeen = el->dateTime_nowMonotonic(el); /* add to hash */ @@ -96,17 +154,79 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, UA_free(listEntry); return UA_STATUSCODE_BADOUTOFMEMORY; } - newHashEntry->next = dm->serverOnNetworkHash[hashIdx]; - dm->serverOnNetworkHash[hashIdx] = newHashEntry; + newHashEntry->next = mdnsPrivateData.serverOnNetworkHash[hashIdx]; + mdnsPrivateData.serverOnNetworkHash[hashIdx] = newHashEntry; newHashEntry->entry = listEntry; - LIST_INSERT_HEAD(&dm->serverOnNetwork, listEntry, pointers); + LIST_INSERT_HEAD(&mdnsPrivateData.serverOnNetwork, listEntry, pointers); if(addedEntry != NULL) *addedEntry = listEntry; + /* call callback for every entry we receive. */ + if(dm->serverOnNetworkCallback) { + dm->serverOnNetworkCallback(&listEntry->serverOnNetwork, true, listEntry->txtSet, + dm->serverOnNetworkCallbackData); + } + return UA_STATUSCODE_GOOD; } +static void +entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, + AVAHI_GCC_UNUSED void *userdata) { + bool groupFound = false; + if(!userdata) + return; + UA_DiscoveryManager *dm = (UA_DiscoveryManager *)userdata; + if(!g) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "AvahiEntryGroup or userdata is NULL"); + return; + } + /* Called whenever the entry group state changes */ + /* Find the registered service on network */ + serverOnNetwork *current; + LIST_FOREACH(current, &mdnsPrivateData.serverOnNetwork, pointers) { + if(current->group == g) { + groupFound = true; + break; + } + } + switch(state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Entry group established."); + break; + case AVAHI_ENTRY_GROUP_COLLISION: { + if(!groupFound) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Entry group collision for unknown group."); + break; + } + char *name = strndup((const char *)current->serverOnNetwork.serverName.data, + current->serverOnNetwork.serverName.length); + UA_Discovery_multicastConflict(name, dm); + break; + } + case AVAHI_ENTRY_GROUP_FAILURE: + UA_LOG_ERROR( + dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + + /* Some kind of failure happened while we were registering our services */ + avahi_simple_poll_quit(mdnsPrivateData.simple_poll); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + default: + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Unknown entry group state"); + break; + } +} + static UA_StatusCode UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, const char *fqdnMdnsRecord, @@ -129,12 +249,12 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, /* remove from hash */ UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; - struct serverOnNetwork_hash_entry *hash_entry = dm->serverOnNetworkHash[hashIdx]; + struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; struct serverOnNetwork_hash_entry *prevEntry = hash_entry; while(hash_entry) { if(hash_entry->entry == entry) { - if(dm->serverOnNetworkHash[hashIdx] == hash_entry) - dm->serverOnNetworkHash[hashIdx] = hash_entry->next; + if(mdnsPrivateData.serverOnNetworkHash[hashIdx] == hash_entry) + mdnsPrivateData.serverOnNetworkHash[hashIdx] = hash_entry->next; else if(prevEntry) prevEntry->next = hash_entry->next; break; @@ -145,7 +265,7 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, UA_free(hash_entry); if(dm->serverOnNetworkCallback && - !UA_String_equal(&dm->selfFqdnMdnsRecord, &recordStr)) + !UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &recordStr)) dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, entry->txtSet, dm->serverOnNetworkCallbackData); @@ -161,17 +281,89 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, return UA_STATUSCODE_GOOD; } -static void -mdns_append_path_to_url(UA_String *url, const char *path) { - size_t pathLen = strlen(path); - size_t newUrlLen = url->length + pathLen; //size of the new url string incl. the path - /* todo: malloc may fail: return a statuscode */ - char *newUrl = (char *)UA_malloc(url->length + pathLen); - memcpy(newUrl, url->data, url->length); - memcpy(newUrl + url->length, path, pathLen); - UA_String_clear(url); - url->length = newUrlLen; - url->data = (UA_Byte *) newUrl; + +UA_StatusCode +UA_DiscoveryManager_clearServerOnNetwork(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return UA_STATUSCODE_BADINVALIDARGUMENT; + } + + serverOnNetwork *son, *son_tmp; + LIST_FOREACH_SAFE(son, &mdnsPrivateData.serverOnNetwork, pointers, son_tmp) { + LIST_REMOVE(son, pointers); + UA_ServerOnNetwork_clear(&son->serverOnNetwork); + if(son->pathTmp) + UA_free(son->pathTmp); + UA_free(son); + } + + UA_String_clear(&mdnsPrivateData.selfMdnsRecord); + + for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { + serverOnNetwork_hash_entry* currHash = mdnsPrivateData.serverOnNetworkHash[i]; + while(currHash) { + serverOnNetwork_hash_entry* nextHash = currHash->next; + UA_free(currHash); + currHash = nextHash; + } + } + + return UA_STATUSCODE_GOOD; +} + +UA_ServerOnNetwork* +UA_DiscoveryManager_getServerOnNetworkList(UA_DiscoveryManager *dm) { + serverOnNetwork* entry = LIST_FIRST(&mdnsPrivateData.serverOnNetwork); + return entry ? &entry->serverOnNetwork : NULL; +} + +UA_ServerOnNetwork* +UA_DiscoveryManager_getNextServerOnNetworkRecord(UA_DiscoveryManager *dm, + UA_ServerOnNetwork *current) { + serverOnNetwork *entry = NULL; + LIST_FOREACH(entry, &mdnsPrivateData.serverOnNetwork, pointers) { + if(&entry->serverOnNetwork == current) { + entry = LIST_NEXT(entry, pointers); + break; + } + } + return entry ? &entry->serverOnNetwork : NULL; +} + + +UA_UInt32 +UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return 0; + } + return mdnsPrivateData.serverOnNetworkRecordIdCounter; +} + +UA_StatusCode +UA_DiscoveryManager_resetServerOnNetworkRecordCounter(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return UA_STATUSCODE_BADINTERNALERROR; + } + mdnsPrivateData.serverOnNetworkRecordIdCounter = 0; + mdnsPrivateData.serverOnNetworkRecordIdLastReset = dm->sc.server->config.eventLoop->dateTime_now( + dm->sc.server->config.eventLoop); + return UA_STATUSCODE_GOOD; +} + +UA_DateTime +UA_DiscoveryManager_getServerOnNetworkCounterResetTime(UA_DiscoveryManager *dm) { + if(!dm) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "DiscoveryManager is NULL"); + return 0; + } + return mdnsPrivateData.serverOnNetworkRecordIdLastReset; } typedef enum { @@ -240,9 +432,31 @@ addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName #define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) #endif +/* Callback when the client state changes */ +static +void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { + /* Handle state changes if necessary */ + UA_LOG_INFO(((UA_DiscoveryManager *)userdata)->sc.server->config.logging, + UA_LOGCATEGORY_DISCOVERY, "Avahi client state changed to %d", state); +} + void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { + int error; + mdnsPrivateData.simple_poll = avahi_simple_poll_new(); + if(!mdnsPrivateData.simple_poll) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to create avahi simple poll"); + return; + } + mdnsPrivateData.client = avahi_client_new(avahi_simple_poll_get(mdnsPrivateData.simple_poll), + AVAHI_CLIENT_NO_FAIL, client_callback, dm, &error); + if(!mdnsPrivateData.client) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to create avahi client: %s", avahi_strerror(error)); + return; + } /* Add record for the server itself */ UA_String appName = dm->sc.server->config.mdnsConfig.mdnsServerName; for(size_t i = 0; i < dm->sc.server->config.serverUrlsSize; i++) @@ -268,13 +482,18 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { UA_Discovery_removeRecord(dm, server->config.mdnsConfig.mdnsServerName, hostname, port, true); } + /* clean up avahi resources */ + if(mdnsPrivateData.client) + avahi_client_free(mdnsPrivateData.client); + if(mdnsPrivateData.simple_poll) + avahi_simple_poll_free(mdnsPrivateData.simple_poll); } void UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm) { /* Clean up the serverOnNetwork list */ serverOnNetwork *son, *son_tmp; - LIST_FOREACH_SAFE(son, &dm->serverOnNetwork, pointers, son_tmp) { + LIST_FOREACH_SAFE(son, &mdnsPrivateData.serverOnNetwork, pointers, son_tmp) { LIST_REMOVE(son, pointers); UA_ServerOnNetwork_clear(&son->serverOnNetwork); if(son->pathTmp) @@ -282,10 +501,10 @@ UA_DiscoveryManager_clearMdns(UA_DiscoveryManager *dm) { UA_free(son); } - UA_String_clear(&dm->selfFqdnMdnsRecord); + UA_String_clear(&mdnsPrivateData.selfMdnsRecord); for(size_t i = 0; i < SERVER_ON_NETWORK_HASH_SIZE; i++) { - serverOnNetwork_hash_entry* currHash = dm->serverOnNetworkHash[i]; + serverOnNetwork_hash_entry* currHash = mdnsPrivateData.serverOnNetworkHash[i]; while(currHash) { serverOnNetwork_hash_entry* nextHash = currHash->next; UA_free(currHash); @@ -350,17 +569,6 @@ UA_Server_setServerOnNetworkCallback(UA_Server *server, UA_UNLOCK(&server->serviceMutex); } -static void -UA_Discovery_multicastConflict(char *name, int type, void *arg) { - /* In case logging is disabled */ - (void)name; - (void)type; - - UA_DiscoveryManager *dm = (UA_DiscoveryManager*) arg; - UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS name conflict detected: " - "'%s' for type %d", name, type); -} void UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data) { @@ -369,10 +577,8 @@ UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data) { /* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ static void -createFullServiceDomain(char *outServiceDomain, size_t maxLen, +createServiceDomain(char *outServiceDomain, size_t maxLen, UA_String servername, UA_String hostname) { - maxLen -= 24; /* the length we have remaining before the opc ua postfix and - * the trailing zero */ /* Can we use hostname and servername with full length? */ if(hostname.length + servername.length + 1 > maxLen) { @@ -395,18 +601,82 @@ createFullServiceDomain(char *outServiceDomain, size_t maxLen, } } else { mp_snprintf(outServiceDomain, maxLen + 1, "%S", servername); - offset = servername.length; } - mp_snprintf(&outServiceDomain[offset], 24, "._opcua-tcp._tcp.local."); } /* Check if mDNS already has an entry for given hostname and port combination */ static UA_Boolean -UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* fullServiceDomain, +UA_Discovery_recordExists(UA_DiscoveryManager *dm, const char* serviceDomain, unsigned short port, const UA_DiscoveryProtocol protocol) { + struct serverOnNetwork *current; + LIST_FOREACH(current, &mdnsPrivateData.serverOnNetwork, pointers) { + if(strcmp((char*)current->serverOnNetwork.serverName.data, serviceDomain) == 0) + return true; + } return false; } +static UA_StatusCode +handle_path(AvahiStringList **txt, const UA_String path) { + if (!path.data || path.length == 0) { + *txt = avahi_string_list_add(*txt, "path=/"); + } else { + char *allocPath = NULL; + if (path.data[0] == '/') { + allocPath = avahi_strdup((const char*) path.data); + } else { + size_t pLen = path.length + 2; + allocPath = (char *) avahi_malloc(pLen); + if(!allocPath) + return UA_STATUSCODE_BADOUTOFMEMORY; + snprintf(allocPath, pLen, "/%s", path.data); + } + size_t pathLen = strlen("path=") + strlen(allocPath) + 1; + char *path_kv = (char *) avahi_malloc(pathLen); + if(!path_kv) { + avahi_free(allocPath); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + snprintf(path_kv, pathLen, "path=%s", allocPath); + *txt = avahi_string_list_add(*txt, path_kv); + avahi_free(allocPath); + avahi_free(path_kv); + } + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +handle_capabilities(AvahiStringList **txt, const UA_String *capabilities, + const size_t capabilitiesSize) { + size_t capsLen = 0; + for (size_t i = 0; i < capabilitiesSize; i++) { + capsLen += capabilities[i].length + 1; // +1 for comma or null terminator + } + char *caps = (char *) avahi_malloc(capsLen); + if(!caps) + return UA_STATUSCODE_BADOUTOFMEMORY; + caps[0] = '\0'; + for (size_t i = 0; i < capabilitiesSize; i++) { + strncat(caps, (const char*)capabilities[i].data, capabilities[i].length); + if (i < capabilitiesSize - 1) { + if (strcat(caps, ",") == NULL) { + avahi_free(caps); + return UA_STATUSCODE_BADINTERNALERROR; + } + } + } + size_t caps_kv_len = strlen("caps=") + strlen(caps) + 1; + char *caps_kv = (char *)avahi_malloc(caps_kv_len); + if(!caps_kv) { + avahi_free(caps); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + snprintf(caps_kv, caps_kv_len, "caps=%s", caps); + *txt = avahi_string_list_add(*txt, caps_kv); + avahi_free(caps); + avahi_free(caps_kv); + return UA_STATUSCODE_GOOD; +} static UA_StatusCode UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, @@ -441,33 +711,31 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, dm->mdnsMainSrvAdded = true; } - /* [servername]-[hostname]._opcua-tcp._tcp.local. */ - char fullServiceDomain[63+24]; - createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + /* [servername]-[hostname] */ + char serviceDomain[63]; + createServiceDomain(serviceDomain, 63, servername, hostname); - UA_Boolean exists = UA_Discovery_recordExists(dm, fullServiceDomain, + UA_Boolean exists = UA_Discovery_recordExists(dm, serviceDomain, port, protocol); if(exists == true) return UA_STATUSCODE_GOOD; UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Multicast DNS: add record for domain: %s", fullServiceDomain); + "Multicast DNS: add record for domain: %s", serviceDomain); - if(isSelf && dm->selfFqdnMdnsRecord.length == 0) { - dm->selfFqdnMdnsRecord = UA_STRING_ALLOC(fullServiceDomain); - if(!dm->selfFqdnMdnsRecord.data) + if(isSelf && mdnsPrivateData.selfMdnsRecord.length == 0) { + mdnsPrivateData.selfMdnsRecord = UA_STRING_ALLOC(serviceDomain); + if(!mdnsPrivateData.selfMdnsRecord.data) return UA_STATUSCODE_BADOUTOFMEMORY; } - UA_String serverName = { - UA_MIN(63, servername.length + hostname.length + 1), - (UA_Byte*) fullServiceDomain}; + UA_String serverName = UA_String_fromChars(serviceDomain); struct serverOnNetwork *listEntry; /* The servername is servername + hostname. It is the same which we get * through mDNS and therefore we need to match servername */ UA_StatusCode retval = - UA_DiscoveryManager_addEntryToServersOnNetwork(dm, fullServiceDomain, + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, serviceDomain, serverName, &listEntry); if(retval != UA_STATUSCODE_GOOD && retval != UA_STATUSCODE_BADALREADYEXISTS) @@ -475,6 +743,11 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, /* If entry is already in list, skip initialization of capabilities and txt+srv */ if(retval != UA_STATUSCODE_BADALREADYEXISTS) { + listEntry->group = avahi_entry_group_new(mdnsPrivateData.client, entry_group_callback, dm); + if(!listEntry->group) { + UA_free(listEntry); + return UA_STATUSCODE_BADRESOURCEUNAVAILABLE; + } /* if capabilitiesSize is 0, then add default cap 'NA' */ listEntry->serverOnNetwork.serverCapabilitiesSize = UA_MAX(1, capabilitiesSize); listEntry->serverOnNetwork.serverCapabilities = (UA_String *) @@ -495,37 +768,79 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, listEntry->txtSet = true; - const size_t newUrlSize = 10 + hostname.length + 8 + path.length + 1; + const size_t newUrlSize = strlen("opc.tcp://") + hostname.length + strlen(".local") + strlen(":port") + path.length + 1; UA_STACKARRAY(char, newUrl, newUrlSize); memset(newUrl, 0, newUrlSize); if(path.length > 0) { - mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S:%d/%S", hostname, port, path); + mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S.local:%d/%S", hostname, port, path); } else { - mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S:%d", hostname, port); + mp_snprintf(newUrl, newUrlSize, "opc.tcp://%S.local:%d", hostname, port); } listEntry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl); listEntry->srvSet = true; } + /* Prepare the TXT records */ + AvahiStringList *txt = NULL; + /* Handle 'path' */ + if(handle_path(&txt, path) != UA_STATUSCODE_GOOD) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add TXT record for %s", serviceDomain); + return UA_STATUSCODE_BADINTERNALERROR; + } + /* Handle 'caps' */ + if (capabilitiesSize > 0) { + if(handle_capabilities(&txt, capabilites, capabilitiesSize) != UA_STATUSCODE_GOOD) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add TXT record for %s", serviceDomain); + return UA_STATUSCODE_BADINTERNALERROR; + } + } else { + txt = avahi_string_list_add(txt, "caps=NA"); + } - /* The first 63 characters of the hostname (or less) */ - size_t maxHostnameLen = UA_MIN(hostname.length, 63); - char localDomain[65]; - memcpy(localDomain, hostname.data, maxHostnameLen); - localDomain[maxHostnameLen] = '.'; - localDomain[maxHostnameLen+1] = '\0'; + /* prepare hostname for avahi */ + size_t hostnameLen = hostname.length + strlen(".local"); + char *hostnameStr = (char *)avahi_malloc(hostnameLen + 1); + if(!hostnameStr) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add TXT record for %s", serviceDomain); + return UA_STATUSCODE_BADINTERNALERROR; + } + memcpy(hostnameStr, hostname.data, hostname.length); + memcpy(hostnameStr + hostname.length, ".local", strlen(".local")); + hostnameStr[hostnameLen] = '\0'; + /*Add the service with TXT records using default host and domain */ + int ret; + if((ret = avahi_entry_group_add_service_strlst( + listEntry->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_PUBLISH_USE_MULTICAST, serviceDomain, "_opcua-tcp._tcp", NULL, hostnameStr, + port, txt)) < 0) { + if(ret == AVAHI_ERR_COLLISION) { + UA_Discovery_multicastConflict(serviceDomain, dm); + } else { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add TXT record for %s", serviceDomain); + goto cleanup; + } + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add TXT record for %s", serviceDomain); + goto cleanup; + } + avahi_free(hostnameStr); - - /* TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,... */ - UA_STACKARRAY(char, pathChars, path.length + 1); - if(createTxt) { - if(path.length > 0) - memcpy(pathChars, path.data, path.length); - pathChars[path.length] = 0; + if (avahi_entry_group_commit(listEntry->group) < 0) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to commit entry group for %s", serviceDomain); + return UA_STATUSCODE_BADINTERNALERROR; } return UA_STATUSCODE_GOOD; + +cleanup: + avahi_free(hostnameStr); + return UA_STATUSCODE_BADINTERNALERROR; } static UA_StatusCode @@ -543,19 +858,17 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, "maximum of 62 chars. It will be truncated."); } - /* [servername]-[hostname]._opcua-tcp._tcp.local. */ - char fullServiceDomain[63 + 24]; - createFullServiceDomain(fullServiceDomain, 63+24, servername, hostname); + /* [servername]-[hostname] */ + char serviceDomain[63]; + createServiceDomain(serviceDomain, 63, servername, hostname); UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Multicast DNS: remove record for domain: %s", - fullServiceDomain); - - UA_String serverName = - {UA_MIN(63, servername.length + hostname.length + 1), (UA_Byte*)fullServiceDomain}; + serviceDomain); + UA_String serverName = UA_String_fromChars(serviceDomain); UA_StatusCode retval = - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, fullServiceDomain, serverName); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, serviceDomain, serverName); return retval; } From afb0f6c0ac9b5802dd10df537b7f5bd1ab7b6f78 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Tue, 19 Nov 2024 14:07:05 +0100 Subject: [PATCH 087/158] refactor(server): Fix host name to remove own record Host name is not included to serverUrls in default config which is used to add a record for own service. While addMdnsRecordForNetworkLayer gets hostname for mDNS record, it is missing for removing mDNS record. Add hostname to UA_DiscoveryManager_stopMulticast and simplify it by using the avahi library. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery_mdns.c | 10 +++++++++- src/server/ua_discovery_mdns_avahi.c | 25 +++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/server/ua_discovery_mdns.c b/src/server/ua_discovery_mdns.c index de6e6e7d2..b507622f5 100644 --- a/src/server/ua_discovery_mdns.c +++ b/src/server/ua_discovery_mdns.c @@ -1088,6 +1088,7 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { UA_Server *server = dm->sc.server; for(size_t i = 0; i < server->config.serverUrlsSize; i++) { UA_String hostname = UA_STRING_NULL; + char hoststr[256]; /* check with UA_MAXHOSTNAME_LENGTH */ UA_String path = UA_STRING_NULL; UA_UInt16 port = 0; @@ -1095,9 +1096,16 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { UA_parseEndpointUrl(&server->config.serverUrls[i], &hostname, &port, &path); - if(retval != UA_STATUSCODE_GOOD || hostname.length == 0) + if(retval != UA_STATUSCODE_GOOD) continue; + if(hostname.length == 0) { + gethostname(hoststr, sizeof(hoststr)-1); + hoststr[sizeof(hoststr)-1] = '\0'; + hostname.data = (unsigned char *) hoststr; + hostname.length = strlen(hoststr); + } + UA_Discovery_removeRecord(dm, server->config.mdnsConfig.mdnsServerName, hostname, port, true); } diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index b794c6561..b98da64e7 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -400,7 +400,6 @@ static UA_StatusCode addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName, const UA_String *discoveryUrl) { UA_String hostname = UA_STRING_NULL; - char hoststr[256]; /* check with UA_MAXHOSTNAME_LENGTH */ UA_UInt16 port = 4840; UA_String path = UA_STRING_NULL; UA_StatusCode retval = @@ -412,10 +411,14 @@ addMdnsRecordForNetworkLayer(UA_DiscoveryManager *dm, const UA_String serverName } if(hostname.length == 0) { - gethostname(hoststr, sizeof(hoststr)-1); - hoststr[sizeof(hoststr)-1] = '\0'; - hostname.data = (unsigned char *) hoststr; - hostname.length = strlen(hoststr); + /* get host name used by avahi */ + const char *hoststr = avahi_client_get_host_name(mdnsPrivateData.client); + if(!hoststr) { + UA_LOG_WARNING(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Cannot get hostname from avahi"); + return UA_STATUSCODE_BADINTERNALERROR; + } + hostname = UA_String_fromChars(hoststr); } retval = UA_Discovery_addRecord(dm, serverName, hostname, port, path, UA_DISCOVERY_TCP, true, dm->sc.server->config.mdnsConfig.serverCapabilities, @@ -476,8 +479,18 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { UA_parseEndpointUrl(&server->config.serverUrls[i], &hostname, &port, &path); - if(retval != UA_STATUSCODE_GOOD || hostname.length == 0) + if(retval != UA_STATUSCODE_GOOD) continue; + if(hostname.length == 0) { + /* get host name used by avahi */ + const char *hoststr = avahi_client_get_host_name(mdnsPrivateData.client); + if(!hoststr) { + UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Cannot get hostname from avahi"); + continue; + } + hostname = UA_String_fromChars(hoststr); + } UA_Discovery_removeRecord(dm, server->config.mdnsConfig.mdnsServerName, hostname, port, true); From 0ed94b2d117dd4e9758b30e9b53303e7eff59dd8 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Fri, 29 Nov 2024 15:09:34 +0100 Subject: [PATCH 088/158] refactor(server): Implement FindServersOnNetwork using avahi-browse Local discovery server with multicast extension should implement FindServersOnNetwork service to provide information about servers on the network. Add avahi-browse to find server announced on the network using mDNS. Use DiscoveryManagers cyclic timer for non blocking single poll of avahi. Resolve OPC UA server announced via mDNS. Take the servername, port and path and create a URL for the server. Also only _opcua-tcp._tcp services are considered, like in the libmdnsd implementation. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery_mdns_avahi.c | 173 ++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 2 deletions(-) diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index b98da64e7..6cd685936 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -44,6 +44,7 @@ typedef struct serverOnNetwork_hash_entry { typedef struct avahiPrivate { AvahiClient *client; AvahiSimplePoll *simple_poll; + AvahiServiceBrowser *browser; UA_Server *server; /* hash mapping domain name to serverOnNetwork list entry */ struct serverOnNetwork_hash_entry* serverOnNetworkHash[SERVER_ON_NETWORK_HASH_SIZE]; @@ -443,6 +444,166 @@ void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { UA_LOGCATEGORY_DISCOVERY, "Avahi client state changed to %d", state); } +/* Called whenever a service has been resolved successfully or timed out */ +static void +resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, + AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, + const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *address, uint16_t port, + AvahiStringList *txt, AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void *userdata) { + + UA_DiscoveryManager *dm = (UA_DiscoveryManager *)userdata; + + switch(event) { + case AVAHI_RESOLVER_FAILURE: + UA_LOG_ERROR( + dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to resolve service '%s' of type '%s' in domain '%s': %s", name, + type, domain, + avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + + case AVAHI_RESOLVER_FOUND: { + char a[AVAHI_ADDRESS_STR_MAX]; + int discoveryLength = 0; + char *path = NULL; + UA_String serverName = UA_String_fromChars(name); + avahi_address_snprint(a, sizeof(a), address); + + /* Ignore own name */ + if(UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &serverName)) { + UA_String_clear(&serverName); + return; + } + struct serverOnNetwork *listEntry; + UA_StatusCode res = UA_DiscoveryManager_addEntryToServersOnNetwork( + dm, name, serverName, &listEntry); + if(res != UA_STATUSCODE_GOOD && res != UA_STATUSCODE_BADALREADYEXISTS) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to add server to ServersOnNetwork: %s", + UA_StatusCode_name(res)); + UA_String_clear(&serverName); + return; + } else if(res == UA_STATUSCODE_BADALREADYEXISTS) { + UA_LOG_DEBUG(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Server already in ServersOnNetwork: %s", name); + UA_String_clear(&serverName); + return; + } + + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Service '%s' of type '%s' in domain '%s':", name, type, domain); + + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + " %s:%u (%s)", host_name, port, a); + /* Add discoveryUrl opc.tcp://[servername]:[port][path] */ + discoveryLength = strlen("opc.tcp://") + strlen(host_name) + 5 + 1; + int listSize = avahi_string_list_length(txt); + for(int i = 0; i < listSize; i++) { + char *value = NULL; + char *key = NULL; + if(avahi_string_list_get_pair(txt, &key, &value, NULL) < 0) + continue; + /* Add path if the is more then just a single slash */ + if(strcmp(key, "path") == 0) { + /* Add path to discovery URL */ + discoveryLength += strlen(value); + path = (char *)UA_malloc(strlen(value) + 1); + if(!path) { + UA_LOG_ERROR(dm->sc.server->config.logging, + UA_LOGCATEGORY_DISCOVERY, + "Failed to allocate memory for path"); + UA_String_clear(&serverName); + return; + } + if(strlen(value) > 1) { + sprintf(path, "%s", value); + } else { /* Ignore empty path */ + path[0] = '\0'; + } + } + + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + " %s = %s", key, value); + avahi_free(key); + avahi_free(value); + txt = avahi_string_list_get_next(txt); + } + + listEntry->lastSeen = dm->sc.server->config.eventLoop->dateTime_nowMonotonic( + dm->sc.server->config.eventLoop); + listEntry->serverOnNetwork.discoveryUrl.length = discoveryLength; + listEntry->serverOnNetwork.discoveryUrl.data = + (UA_Byte *)UA_malloc(discoveryLength); + if(!listEntry->serverOnNetwork.discoveryUrl.data) { + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to allocate memory for discoveryUrl"); + UA_free(path); + UA_String_clear(&serverName); + return; + } + sprintf((char *)listEntry->serverOnNetwork.discoveryUrl.data, + "opc.tcp://%s:%u%s", host_name, port, path); + UA_free(path); + UA_String_clear(&serverName); + break; + } + default: + UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Invalid avahi resolver event %d", event); + break; + + } + avahi_service_resolver_free(r); +} + +static void +browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, const char *name, const char *type, + const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + void *userdata) { + UA_DiscoveryManager *dm = (UA_DiscoveryManager *)userdata; + switch(event) { + case AVAHI_BROWSER_FAILURE: + UA_LOG_ERROR( + dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Browser failure: %s", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + avahi_simple_poll_quit(mdnsPrivateData.simple_poll); + return; + case AVAHI_BROWSER_NEW: + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ + if(!(avahi_service_resolver_new( + mdnsPrivateData.client, interface, protocol, name, type, domain, AVAHI_PROTO_INET, + AVAHI_LOOKUP_USE_MULTICAST, resolve_callback, dm))) { + UA_LOG_INFO( + dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Failed to resolve service '%s' of type '%s' in domain '%s': %s", + name, type, domain, avahi_strerror(avahi_client_errno(mdnsPrivateData.client))); + } + break; + case AVAHI_BROWSER_REMOVE: + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Service '%s' of type '%s' in domain '%s' removed", name, type, + domain); + UA_String nameStr = UA_STRING_ALLOC(name); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, name, UA_STRING_NULL); + UA_String_clear(&nameStr); + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Browser %s", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" + : "ALL_FOR_NOW"); + break; + } +} + void UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { int error; @@ -465,6 +626,9 @@ UA_DiscoveryManager_startMulticast(UA_DiscoveryManager *dm) { for(size_t i = 0; i < dm->sc.server->config.serverUrlsSize; i++) addMdnsRecordForNetworkLayer(dm, appName, &dm->sc.server->config.serverUrls[i]); + mdnsPrivateData.browser = avahi_service_browser_new(mdnsPrivateData.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, + "_opcua-tcp._tcp", NULL, AVAHI_LOOKUP_USE_MULTICAST, + browse_callback, dm); } void @@ -496,6 +660,8 @@ UA_DiscoveryManager_stopMulticast(UA_DiscoveryManager *dm) { hostname, port, true); } /* clean up avahi resources */ + if(mdnsPrivateData.browser) + avahi_service_browser_free(mdnsPrivateData.browser); if(mdnsPrivateData.client) avahi_client_free(mdnsPrivateData.client); if(mdnsPrivateData.simple_poll) @@ -582,10 +748,13 @@ UA_Server_setServerOnNetworkCallback(UA_Server *server, UA_UNLOCK(&server->serviceMutex); } - void UA_DiscoveryManager_mdnsCyclicTimer(UA_Server *server, void *data) { - + int ret = avahi_simple_poll_iterate(mdnsPrivateData.simple_poll, 0); + if(ret < 0) { + UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_DISCOVERY, + "Error in avahi_simple_poll_iterate: %s", avahi_strerror(ret)); + } } /* Create a service domain with the format [servername]-[hostname]._opcua-tcp._tcp.local. */ From ba1e149fab02e5efadaace72b0e1ba169738b0b7 Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Fri, 29 Nov 2024 16:01:03 +0100 Subject: [PATCH 089/158] refactor(server): Simplify internal record handling for avahi mDNS Fully domain name is handled by avahi, therefore serverName and mDNS record have the same information. Simplify internal record handling by removing obsolete variables. Signed-off-by: Vasilij Strassheim --- src/server/ua_discovery_mdns_avahi.c | 56 +++++++++++----------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/server/ua_discovery_mdns_avahi.c b/src/server/ua_discovery_mdns_avahi.c index 6cd685936..73f2b6b95 100644 --- a/src/server/ua_discovery_mdns_avahi.c +++ b/src/server/ua_discovery_mdns_avahi.c @@ -61,7 +61,6 @@ avahiPrivate mdnsPrivateData; static UA_StatusCode UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, UA_String serverName, struct serverOnNetwork **addedEntry); @@ -73,12 +72,11 @@ UA_Discovery_multicastConflict(char *name, UA_DiscoveryManager *dm) { "Multicast DNS name conflict detected: '%s'", name); } - static struct serverOnNetwork * -mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, - UA_String serverName, UA_Boolean createNew) { - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)record, - strlen(record)) % SERVER_ON_NETWORK_HASH_SIZE; +mdns_record_add_or_get(UA_DiscoveryManager *dm, UA_String serverName, + UA_Boolean createNew) { + UA_UInt32 hashIdx = UA_ByteString_hash(0, serverName.data, + serverName.length) % SERVER_ON_NETWORK_HASH_SIZE; struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; while(hash_entry) { @@ -97,21 +95,19 @@ mdns_record_add_or_get(UA_DiscoveryManager *dm, const char *record, struct serverOnNetwork *listEntry; UA_StatusCode res = - UA_DiscoveryManager_addEntryToServersOnNetwork(dm, record, serverName, &listEntry); + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, serverName, &listEntry); if(res != UA_STATUSCODE_GOOD) return NULL; return listEntry; } - static UA_StatusCode UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, UA_String serverName, struct serverOnNetwork **addedEntry) { struct serverOnNetwork *entry = - mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, false); + mdns_record_add_or_get(dm, serverName, false); if(entry) { if(addedEntry != NULL) *addedEntry = entry; @@ -119,8 +115,7 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, } UA_LOG_DEBUG(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: Add entry to ServersOnNetwork: %s (%S)", - fqdnMdnsRecord, serverName); + "Multicast DNS: Add entry to ServersOnNetwork: (%S)", serverName); struct serverOnNetwork *listEntry = (serverOnNetwork*) UA_malloc(sizeof(struct serverOnNetwork)); @@ -146,8 +141,8 @@ UA_DiscoveryManager_addEntryToServersOnNetwork(UA_DiscoveryManager *dm, listEntry->lastSeen = el->dateTime_nowMonotonic(el); /* add to hash */ - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)fqdnMdnsRecord, - strlen(fqdnMdnsRecord)) % SERVER_ON_NETWORK_HASH_SIZE; + UA_UInt32 hashIdx = UA_ByteString_hash(0, serverName.data, serverName.length) % + SERVER_ON_NETWORK_HASH_SIZE; struct serverOnNetwork_hash_entry *newHashEntry = (struct serverOnNetwork_hash_entry*) UA_malloc(sizeof(struct serverOnNetwork_hash_entry)); if(!newHashEntry) { @@ -230,26 +225,18 @@ entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, static UA_StatusCode UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, - const char *fqdnMdnsRecord, UA_String serverName) { UA_LOG_DEBUG(dm->sc.server->config.logging, UA_LOGCATEGORY_SERVER, - "Multicast DNS: Remove entry from ServersOnNetwork: %s (%S)", - fqdnMdnsRecord, serverName); + "Multicast DNS: Remove entry from ServersOnNetwork: %S",serverName); struct serverOnNetwork *entry = - mdns_record_add_or_get(dm, fqdnMdnsRecord, serverName, false); + mdns_record_add_or_get(dm, serverName, false); if(!entry) return UA_STATUSCODE_BADNOTFOUND; - UA_String recordStr; - // Cast away const because otherwise the pointer cannot be assigned. - // Be careful what you do with recordStr! - recordStr.data = (UA_Byte*)(uintptr_t)fqdnMdnsRecord; - recordStr.length = strlen(fqdnMdnsRecord); - /* remove from hash */ - UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)recordStr.data, - recordStr.length) % SERVER_ON_NETWORK_HASH_SIZE; + UA_UInt32 hashIdx = UA_ByteString_hash(0, (const UA_Byte*)serverName.data, + serverName.length) % SERVER_ON_NETWORK_HASH_SIZE; struct serverOnNetwork_hash_entry *hash_entry = mdnsPrivateData.serverOnNetworkHash[hashIdx]; struct serverOnNetwork_hash_entry *prevEntry = hash_entry; while(hash_entry) { @@ -266,7 +253,7 @@ UA_DiscoveryManager_removeEntryFromServersOnNetwork(UA_DiscoveryManager *dm, UA_free(hash_entry); if(dm->serverOnNetworkCallback && - !UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &recordStr)) + !UA_String_equal(&mdnsPrivateData.selfMdnsRecord, &serverName)) dm->serverOnNetworkCallback(&entry->serverOnNetwork, false, entry->txtSet, dm->serverOnNetworkCallbackData); @@ -478,7 +465,7 @@ resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interfac } struct serverOnNetwork *listEntry; UA_StatusCode res = UA_DiscoveryManager_addEntryToServersOnNetwork( - dm, name, serverName, &listEntry); + dm, serverName, &listEntry); if(res != UA_STATUSCODE_GOOD && res != UA_STATUSCODE_BADALREADYEXISTS) { UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Failed to add server to ServersOnNetwork: %s", @@ -591,7 +578,7 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol pr "Service '%s' of type '%s' in domain '%s' removed", name, type, domain); UA_String nameStr = UA_STRING_ALLOC(name); - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, name, UA_STRING_NULL); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, nameStr); UA_String_clear(&nameStr); break; case AVAHI_BROWSER_ALL_FOR_NOW: @@ -905,20 +892,19 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, UA_LOG_INFO(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, "Multicast DNS: add record for domain: %s", serviceDomain); + UA_String serverName = UA_String_fromChars(serviceDomain); if(isSelf && mdnsPrivateData.selfMdnsRecord.length == 0) { - mdnsPrivateData.selfMdnsRecord = UA_STRING_ALLOC(serviceDomain); + mdnsPrivateData.selfMdnsRecord = serverName; if(!mdnsPrivateData.selfMdnsRecord.data) return UA_STATUSCODE_BADOUTOFMEMORY; } - UA_String serverName = UA_String_fromChars(serviceDomain); struct serverOnNetwork *listEntry; /* The servername is servername + hostname. It is the same which we get * through mDNS and therefore we need to match servername */ UA_StatusCode retval = - UA_DiscoveryManager_addEntryToServersOnNetwork(dm, serviceDomain, - serverName, &listEntry); + UA_DiscoveryManager_addEntryToServersOnNetwork(dm, serverName, &listEntry); if(retval != UA_STATUSCODE_GOOD && retval != UA_STATUSCODE_BADALREADYEXISTS) return retval; @@ -967,7 +953,7 @@ UA_Discovery_addRecord(UA_DiscoveryManager *dm, const UA_String servername, /* Handle 'path' */ if(handle_path(&txt, path) != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(dm->sc.server->config.logging, UA_LOGCATEGORY_DISCOVERY, - "Failed to add TXT record for %s", serviceDomain); + "Failed to add TXT record for %S", serverName); return UA_STATUSCODE_BADINTERNALERROR; } /* Handle 'caps' */ @@ -1050,7 +1036,7 @@ UA_Discovery_removeRecord(UA_DiscoveryManager *dm, const UA_String servername, UA_String serverName = UA_String_fromChars(serviceDomain); UA_StatusCode retval = - UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, serviceDomain, serverName); + UA_DiscoveryManager_removeEntryFromServersOnNetwork(dm, serverName); return retval; } From 892c8956a6175b3d2add4ca164c17681bb49b57a Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Thu, 14 Nov 2024 16:21:42 +0100 Subject: [PATCH 090/158] docs: Add build instructions for avahi Signed-off-by: Vasilij Strassheim --- doc/building.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/building.rst b/doc/building.rst index 0b1a04601..99c263d4d 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -23,6 +23,7 @@ Building with CMake on Ubuntu or Debian sudo apt-get install check libsubunit-dev # for unit tests sudo apt-get install python3-sphinx graphviz # for documentation generation sudo apt-get install python3-sphinx-rtd-theme # documentation style + sudo apt-get install libavahi-client-dev libavahi-common-dev # for LDS-ME (multicast discovery) git clone https://github.com/open62541/open62541.git cd open62541 From ae5bc9dd8671980816c7d9074ac55eaf0bd14277 Mon Sep 17 00:00:00 2001 From: max65482 <56191773+max65482@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:52:01 +0100 Subject: [PATCH 091/158] refactor(core): Prevent duplicate clear in sendSymmetricChunk (#6998) --- src/ua_securechannel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index 9f841b73e..cd7b78bd3 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -441,6 +441,7 @@ sendSymmetricChunk(UA_MessageContext *mc) { &UA_KEYVALUEMAP_NULL, &mc->messageBuffer); if(res != UA_STATUSCODE_GOOD && UA_SecureChannel_isConnected(channel)) channel->state = UA_SECURECHANNELSTATE_CLOSING; + return res; error: /* Free the unused message buffer */ From aa5226451017ae28ae855f3c99b709ab4c981cfb Mon Sep 17 00:00:00 2001 From: Vasilij Strassheim Date: Thu, 2 Jan 2025 12:26:52 +0100 Subject: [PATCH 092/158] build(nc): fix UA_FILE_NS0_PRIVATE variable unset According to the CMake documentation: "unsetting a normal variable can expose a cache variable that was previously hidden". This can lead to wrong build results if UA_NAMESPACE_ZERO is changed to FULL after configuring the project with default REDUCED value. This happens e.g. if ccmake is used like shown in the documentation. Set UA_FILE_NS0_PRIVATE to "" like proposed in the CMake documentation to prevent this behavior. Signed-off-by: Vasilij Strassheim --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd1ae0712..d34ccf3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1086,7 +1086,7 @@ endif() set(UA_FILE_NODESETS) # List of nodeset-xml files to be considered in the generated information model set(UA_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset CACHE STRING "The path to the node-set directory (e.g. from https://github.com/OPCFoundation/UA-Nodeset)") -unset(UA_FILE_NS0_PRIVATE) +set(UA_FILE_NS0_PRIVATE "") if(UA_FILE_NS0) set(UA_FILE_NS0_PRIVATE "${UA_FILE_NS0}") endif() From 2ff9d6f8cb71acbe9c6fffa8b0d89e30024ed76f Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 25 Dec 2024 22:55:04 +0100 Subject: [PATCH 093/158] refactor(core): NamespaceMapping methods take const pointers --- include/open62541/types.h | 8 ++++---- src/ua_types.c | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 21d3ad9b5..7c305a0ab 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -1283,23 +1283,23 @@ typedef struct { /* If the index is unknown, returns (UINT16_MAX - index) */ UA_UInt16 -UA_NamespaceMapping_local2Remote(UA_NamespaceMapping *nm, +UA_NamespaceMapping_local2Remote(const UA_NamespaceMapping *nm, UA_UInt16 localIndex); UA_UInt16 -UA_NamespaceMapping_remote2Local(UA_NamespaceMapping *nm, +UA_NamespaceMapping_remote2Local(const UA_NamespaceMapping *nm, UA_UInt16 remoteIndex); /* Returns an error if the namespace uri was not found. * The pointer to the index argument needs to be non-NULL. */ UA_StatusCode -UA_NamespaceMapping_uri2Index(UA_NamespaceMapping *nm, +UA_NamespaceMapping_uri2Index(const UA_NamespaceMapping *nm, UA_String uri, UA_UInt16 *index); /* Upon success, the uri string gets set. The string is not copied and must not * outlive the namespace mapping structure. */ UA_StatusCode -UA_NamespaceMapping_index2Uri(UA_NamespaceMapping *nm, +UA_NamespaceMapping_index2Uri(const UA_NamespaceMapping *nm, UA_UInt16 index, UA_String *uri); void diff --git a/src/ua_types.c b/src/ua_types.c index ad177ba4b..9034aacc3 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -2286,14 +2286,16 @@ UA_NumericRange_parse(UA_NumericRange *range, const UA_String str) { /*********************/ UA_UInt16 -UA_NamespaceMapping_local2Remote(UA_NamespaceMapping *nm, UA_UInt16 localIndex) { +UA_NamespaceMapping_local2Remote(const UA_NamespaceMapping *nm, + UA_UInt16 localIndex) { if(localIndex >= nm->local2remoteSize) return UA_UINT16_MAX - localIndex; return nm->local2remote[localIndex]; } UA_UInt16 -UA_NamespaceMapping_remote2Local(UA_NamespaceMapping *nm, UA_UInt16 remoteIndex) { +UA_NamespaceMapping_remote2Local(const UA_NamespaceMapping *nm, + UA_UInt16 remoteIndex) { if(remoteIndex >= nm->remote2localSize) return UA_UINT16_MAX - remoteIndex; return nm->remote2local[remoteIndex]; @@ -2302,7 +2304,8 @@ UA_NamespaceMapping_remote2Local(UA_NamespaceMapping *nm, UA_UInt16 remoteIndex) /* Returns an error if the uri was not found. * The pointer to the index argument needs to be non-NULL. */ UA_StatusCode -UA_NamespaceMapping_uri2Index(UA_NamespaceMapping *nm, UA_String uri, UA_UInt16 *index) { +UA_NamespaceMapping_uri2Index(const UA_NamespaceMapping *nm, + UA_String uri, UA_UInt16 *index) { for(size_t i = 0; i < nm->namespaceUrisSize; i++) { if(UA_String_equal(&uri, &nm->namespaceUris[i])) { *index = (UA_UInt16)i; @@ -2313,7 +2316,8 @@ UA_NamespaceMapping_uri2Index(UA_NamespaceMapping *nm, UA_String uri, UA_UInt16 } UA_StatusCode -UA_NamespaceMapping_index2Uri(UA_NamespaceMapping *nm, UA_UInt16 index, UA_String *uri) { +UA_NamespaceMapping_index2Uri(const UA_NamespaceMapping *nm, + UA_UInt16 index, UA_String *uri) { if(nm->namespaceUrisSize <= index) return UA_STATUSCODE_BADNOTFOUND; *uri = nm->namespaceUris[index]; From da73aeb4f0e0009ee4bc89ed705712d3425fdd23 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 25 Dec 2024 13:50:00 +0100 Subject: [PATCH 094/158] feat(core): Add extended encoding of NodeIds using the namespace mapping --- include/open62541/types.h | 32 ++++++++++++-- src/ua_types.c | 91 ++++++++++++++++++++++++++------------- tests/check_utils.c | 73 ++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 35 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 7c305a0ab..90086abe2 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -21,6 +21,9 @@ #include #include +struct UA_NamespaceMapping; +typedef struct UA_NamespaceMapping UA_NamespaceMapping; + _UA_BEGIN_DECLS /** @@ -417,8 +420,7 @@ UA_EXPORT extern const UA_NodeId UA_NODEID_NULL; UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p); -/* Print the NodeId in the human-readable format defined in Part 6, - * 5.3.1.10. +/* Print the NodeId in the human-readable format defined in Part 6. * * Examples: * UA_NODEID("i=13") @@ -432,6 +434,17 @@ UA_Boolean UA_EXPORT UA_NodeId_isNull(const UA_NodeId *p); UA_StatusCode UA_EXPORT UA_NodeId_print(const UA_NodeId *id, UA_String *output); +/* Extended NodeId printing. If nsMapping argument is non-NULL, then the + * NamespaceIndex is translated to the NamespaceUri. If that is not successful, + * the numerical NamespaceIndex is used instead. + * + * Examples: + * nsu=http://widgets.com/schemas/hello;s=Hello World + */ +UA_StatusCode UA_EXPORT +UA_NodeId_printEx(const UA_NodeId *id, UA_String *output, + const UA_NamespaceMapping *nsMapping); + /* Parse the human-readable NodeId format. Attention! String and * ByteString NodeIds have their identifier malloc'ed and need to be * cleaned up. */ @@ -550,6 +563,17 @@ UA_EXPORT extern const UA_ExpandedNodeId UA_EXPANDEDNODEID_NULL; UA_StatusCode UA_EXPORT UA_ExpandedNodeId_print(const UA_ExpandedNodeId *id, UA_String *output); +/* Extended printing of ExpandedNodeId. It tries to map NamespaceIndex and + * ServerIndex to a Uri using the provided mapping. + * + * Examples: + * svu=http://smith.com/west/factory;nsu=tag:acme.com,2023;i=1234 + */ +UA_StatusCode UA_EXPORT +UA_ExpandedNodeId_printEx(const UA_ExpandedNodeId *id, UA_String *output, + const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris); + /* Parse the human-readable NodeId format. Attention! String and * ByteString NodeIds have their identifier malloc'ed and need to be * cleaned up. */ @@ -1267,7 +1291,7 @@ UA_INLINABLE(UA_Boolean * translate the ``NamespaceUri`` field of an ExpandedNodeId into the namespace * index of the NodeId embedded in the ExpandedNodeId. */ -typedef struct { +struct UA_NamespaceMapping { /* Namespaces with their local index */ UA_String *namespaceUris; size_t namespaceUrisSize; @@ -1279,7 +1303,7 @@ typedef struct { /* Map from remote to local indices */ UA_UInt16 *remote2local; size_t remote2localSize; -} UA_NamespaceMapping; +}; /* If the index is unknown, returns (UINT16_MAX - index) */ UA_UInt16 diff --git a/src/ua_types.c b/src/ua_types.c index 9034aacc3..7910bcb6f 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -457,10 +457,14 @@ UA_NodeId_hash(const UA_NodeId *n) { static size_t nodeIdSize(const UA_NodeId *id, char *nsStr, size_t *nsStrSize, - char *numIdStr, size_t *numIdStrSize) { + char *numIdStr, size_t *numIdStrSize, + UA_String nsUri) { /* Namespace length */ size_t len = 0; - if(id->namespaceIndex != 0) { + if(nsUri.length > 0) { + len += 5; /* nsu=; */ + len += nsUri.length; + } else if(id->namespaceIndex > 0) { len += 4; /* ns=; */ *nsStrSize = itoaUnsigned(id->namespaceIndex, nsStr, 10); len += *nsStrSize; @@ -488,7 +492,13 @@ nodeIdSize(const UA_NodeId *id, #define PRINT_NODEID \ /* Encode the namespace */ \ - if(id->namespaceIndex != 0) { \ + if(nsUri.length > 0) { \ + memcpy(pos, "nsu=", 4); \ + pos += 4; \ + memcpy(pos, nsUri.data, nsUri.length); \ + pos += nsUri.length; \ + *pos++ = ';'; \ + } else if(id->namespaceIndex > 0) { \ memcpy(pos, "ns=", 3); \ pos += 3; \ memcpy(pos, nsStr, nsStrSize); \ @@ -529,13 +539,19 @@ nodeIdSize(const UA_NodeId *id, do { } while(false) UA_StatusCode -UA_NodeId_print(const UA_NodeId *id, UA_String *output) { - /* Compute the string length */ +UA_NodeId_printEx(const UA_NodeId *id, UA_String *output, + const UA_NamespaceMapping *nsMapping) { + /* Try to map the NamespaceIndex to the Uri */ + UA_String nsUri = UA_STRING_NULL; + if(id->namespaceIndex > 0 && nsMapping) + UA_NamespaceMapping_index2Uri(nsMapping, id->namespaceIndex, &nsUri); + + /* Compute the string length and print numerical identifiers */ char nsStr[6]; size_t nsStrSize = 0; char numIdStr[11]; size_t numIdStrSize = 0; - size_t idLen = nodeIdSize(id, nsStr, &nsStrSize, numIdStr, &numIdStrSize); + size_t idLen = nodeIdSize(id, nsStr, &nsStrSize, numIdStr, &numIdStrSize, nsUri); if(idLen == 0) return UA_STATUSCODE_BADINTERNALERROR; @@ -558,6 +574,11 @@ UA_NodeId_print(const UA_NodeId *id, UA_String *output) { return UA_STATUSCODE_GOOD; } +UA_StatusCode +UA_NodeId_print(const UA_NodeId *id, UA_String *output) { + return UA_NodeId_printEx(id, output, NULL); +} + /* ExpandedNodeId */ static void ExpandedNodeId_clear(UA_ExpandedNodeId *p, const UA_DataType *_) { @@ -596,35 +617,43 @@ UA_ExpandedNodeId_hash(const UA_ExpandedNodeId *n) { } UA_StatusCode -UA_ExpandedNodeId_print(const UA_ExpandedNodeId *eid, UA_String *output) { - /* Don't print the namespace-index if a NamespaceUri is set */ - UA_NodeId stackid = eid->nodeId; - UA_NodeId *id = &stackid; /* for the print-macro below */ - if(eid->namespaceUri.data != NULL) - id->namespaceIndex = 0; +UA_ExpandedNodeId_printEx(const UA_ExpandedNodeId *eid, UA_String *output, + const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris) { + /* Shortahdn and for the print-macro below */ + const UA_NodeId *id = &eid->nodeId; - /* Compute the string length */ + /* Try to map the NamespaceIndex to the Uri */ + UA_String nsUri = eid->namespaceUri; + if(nsUri.length == 0 && id->namespaceIndex > 0 && nsMapping) + UA_NamespaceMapping_index2Uri(nsMapping, id->namespaceIndex, &nsUri); + + /* Try to map the ServerIndex to a Uri */ + UA_String srvUri = UA_STRING_NULL; + if(eid->serverIndex > 0 && eid->serverIndex < serverUrisSize) + srvUri = serverUris[eid->serverIndex]; + + /* Compute the NodeId string length */ char nsStr[6]; size_t nsStrSize = 0; char numIdStr[11]; size_t numIdStrSize = 0; - size_t idLen = nodeIdSize(id, nsStr, &nsStrSize, numIdStr, &numIdStrSize); + size_t idLen = nodeIdSize(id, nsStr, &nsStrSize, numIdStr, &numIdStrSize, nsUri); if(idLen == 0) return UA_STATUSCODE_BADINTERNALERROR; char srvIdxStr[11]; size_t srvIdxSize = 0; - if(eid->serverIndex != 0) { + if(srvUri.length > 0) { + idLen += 5; /* svu=; */ + idLen += srvUri.length; + idLen += srvIdxSize; + } else if(eid->serverIndex > 0) { idLen += 5; /* svr=; */ srvIdxSize = itoaUnsigned(eid->serverIndex, srvIdxStr, 10); idLen += srvIdxSize; } - if(eid->namespaceUri.data != NULL) { - idLen += 5; /* nsu=; */ - idLen += eid->namespaceUri.length; - } - /* Allocate memory if required */ if(output->length == 0) { UA_StatusCode res = UA_ByteString_allocBuffer((UA_ByteString*)output, idLen); @@ -638,7 +667,13 @@ UA_ExpandedNodeId_print(const UA_ExpandedNodeId *eid, UA_String *output) { /* Encode the ServerIndex */ char *pos = (char*)output->data; - if(eid->serverIndex != 0) { + if(srvUri.length > 0) { + memcpy(pos, "svu=", 4); + pos += 4; + memcpy(pos, srvUri.data, srvUri.length); + pos += srvUri.length; + *pos++ = ';'; + } else if(eid->serverIndex > 0) { memcpy(pos, "svr=", 4); pos += 4; memcpy(pos, srvIdxStr, srvIdxSize); @@ -646,15 +681,6 @@ UA_ExpandedNodeId_print(const UA_ExpandedNodeId *eid, UA_String *output) { *pos++ = ';'; } - /* Encode the NamespaceUri */ - if(eid->namespaceUri.data != NULL) { - memcpy(pos, "nsu=", 4); - pos += 4; - memcpy(pos, eid->namespaceUri.data, eid->namespaceUri.length); - pos += eid->namespaceUri.length; - *pos++ = ';'; - } - /* Print the NodeId */ PRINT_NODEID; @@ -662,6 +688,11 @@ UA_ExpandedNodeId_print(const UA_ExpandedNodeId *eid, UA_String *output) { return UA_STATUSCODE_GOOD; } +UA_StatusCode +UA_ExpandedNodeId_print(const UA_ExpandedNodeId *eid, UA_String *output) { + return UA_ExpandedNodeId_printEx(eid, output, NULL, 0, NULL); +} + /* ExtensionObject */ static void ExtensionObject_clear(UA_ExtensionObject *p, const UA_DataType *_) { diff --git a/tests/check_utils.c b/tests/check_utils.c index 6b44bcf00..5e4cde2fb 100644 --- a/tests/check_utils.c +++ b/tests/check_utils.c @@ -435,6 +435,31 @@ START_TEST(idToStringByte) { UA_String_clear(&str); } END_TEST +START_TEST(idToStringWithMapping) { + UA_NamespaceMapping nsMapping; + memset(&nsMapping, 0, sizeof(UA_NamespaceMapping)); + + UA_String namespaces[2] = { + UA_STRING_STATIC("ns1"), + UA_STRING_STATIC("ns2") + }; + + nsMapping.namespaceUris = namespaces; + nsMapping.namespaceUrisSize = 2; + + UA_NodeId n; + UA_String str = UA_STRING_NULL; + + n = UA_NODEID_NUMERIC(1,1234567890); + UA_NodeId_printEx(&n, &str, &nsMapping); + assertNodeIdString(&str, "nsu=ns2;i=1234567890"); + UA_String_clear(&str); + + n = UA_NODEID_NUMERIC(0xFFFF,0xFFFFFFFF); + UA_NodeId_print(&n, &str); + assertNodeIdString(&str, "ns=65535;i=4294967295"); + UA_String_clear(&str); +} END_TEST START_TEST(idOrderNs) { UA_NodeId id_ns1 = UA_NODEID_NUMERIC(1, 12345); @@ -567,6 +592,47 @@ START_TEST(idOrderString) { ck_assert(UA_NodeId_order(&id_str_d, &id_str_c) == UA_ORDER_MORE); } END_TEST +START_TEST(expIdToStringNumeric) { + UA_ExpandedNodeId n; + UA_String str = UA_STRING_NULL; + + n = UA_EXPANDEDNODEID_NUMERIC(0,0); + UA_ExpandedNodeId_print(&n, &str); + assertNodeIdString(&str, "i=0"); + UA_String_clear(&str); + + n.serverIndex = 1; + UA_ExpandedNodeId_print(&n, &str); + assertNodeIdString(&str, "svr=1;i=0"); + UA_String_clear(&str); + + n.namespaceUri = UA_STRING("testuri"); + UA_ExpandedNodeId_print(&n, &str); + assertNodeIdString(&str, "svr=1;nsu=testuri;i=0"); + UA_String_clear(&str); +} END_TEST + +START_TEST(expIdToStringNumericWithMapping) { + UA_String serverUris[2] = { + UA_STRING_STATIC("uri:server1"), + UA_STRING_STATIC("uri:server2") + }; + + UA_ExpandedNodeId n; + UA_String str = UA_STRING_NULL; + + n = UA_EXPANDEDNODEID_NUMERIC(0,0); + n.serverIndex = 1; + UA_ExpandedNodeId_printEx(&n, &str, NULL, 2, serverUris); + assertNodeIdString(&str, "svu=uri:server2;i=0"); + UA_String_clear(&str); + + n.namespaceUri = UA_STRING("testuri"); + UA_ExpandedNodeId_printEx(&n, &str, NULL, 2, serverUris); + assertNodeIdString(&str, "svu=uri:server2;nsu=testuri;i=0"); + UA_String_clear(&str); +} END_TEST + static Suite* testSuite_Utils(void) { Suite *s = suite_create("Utils"); TCase *tc_endpointUrl_split = tcase_create("EndpointUrl_split"); @@ -582,12 +648,12 @@ static Suite* testSuite_Utils(void) { tcase_add_test(tc_utils, stringCompare); suite_add_tcase(s,tc_utils); - TCase *tc1 = tcase_create("test nodeid string"); tcase_add_test(tc1, idToStringNumeric); tcase_add_test(tc1, idToStringString); tcase_add_test(tc1, idToStringGuid); tcase_add_test(tc1, idToStringByte); + tcase_add_test(tc1, idToStringWithMapping); suite_add_tcase(s, tc1); TCase *tc2 = tcase_create("test nodeid order"); @@ -598,6 +664,11 @@ static Suite* testSuite_Utils(void) { tcase_add_test(tc1, idOrderString); suite_add_tcase(s, tc2); + TCase *tc3 = tcase_create("test expandednodeid string"); + tcase_add_test(tc3, expIdToStringNumeric); + tcase_add_test(tc3, expIdToStringNumericWithMapping); + suite_add_tcase(s, tc3); + return s; } From af71a4d6ed91b3d03ece3f690938229fde0c5528 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 26 Dec 2024 16:52:52 +0100 Subject: [PATCH 095/158] feat(core): Extended parsing of ExpandedNodeId with NamespaceUri and ServerUri mapping --- include/open62541/types.h | 24 +- src/util/ua_types_lex.c | 587 ++++++++++++++++++++++---------------- src/util/ua_types_lex.re | 172 +++++++---- tests/check_utils.c | 18 +- 4 files changed, 499 insertions(+), 302 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 90086abe2..0c8080def 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -445,13 +445,28 @@ UA_StatusCode UA_EXPORT UA_NodeId_printEx(const UA_NodeId *id, UA_String *output, const UA_NamespaceMapping *nsMapping); +#ifdef UA_ENABLE_PARSING /* Parse the human-readable NodeId format. Attention! String and * ByteString NodeIds have their identifier malloc'ed and need to be * cleaned up. */ -#ifdef UA_ENABLE_PARSING UA_StatusCode UA_EXPORT UA_NodeId_parse(UA_NodeId *id, const UA_String str); +/* Extended parsing that uses the provided namespace mapping to find the + * NamespaceIndex for a provided NamespaceUri. + * + * If the NodeId uses an unknown NamespaceUri, then a String-NodeId is returned + * that uses NamespaceIndex 0 and the full original encoding for the string + * part. + * + * Example: + * nsu=my_uri;i=5 => s="nsu=my_uri;i=5" (The quotation marks are for + * illustration purposes and not actually included) + */ +UA_StatusCode UA_EXPORT +UA_NodeId_parseEx(UA_NodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping); + UA_INLINABLE(UA_NodeId UA_NODEID(const char *chars), { UA_NodeId id; @@ -574,13 +589,18 @@ UA_ExpandedNodeId_printEx(const UA_ExpandedNodeId *id, UA_String *output, const UA_NamespaceMapping *nsMapping, size_t serverUrisSize, const UA_String *serverUris); +#ifdef UA_ENABLE_PARSING /* Parse the human-readable NodeId format. Attention! String and * ByteString NodeIds have their identifier malloc'ed and need to be * cleaned up. */ -#ifdef UA_ENABLE_PARSING UA_StatusCode UA_EXPORT UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str); +UA_StatusCode UA_EXPORT +UA_ExpandedNodeId_parseEx(UA_ExpandedNodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris); + UA_INLINABLE(UA_ExpandedNodeId UA_EXPANDEDNODEID(const char *chars), { UA_ExpandedNodeId id; diff --git a/src/util/ua_types_lex.c b/src/util/ua_types_lex.c index a49248e8b..ded210f29 100644 --- a/src/util/ua_types_lex.c +++ b/src/util/ua_types_lex.c @@ -1,4 +1,4 @@ -/* Generated by re2c 3.1 */ +/* Generated by re2c 4.0.2 */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -38,7 +38,7 @@ typedef struct { const char *marker; - const char *yyt1;const char *yyt2;const char *yyt3;const char *yyt4; + const char *yyt1;const char *yyt2;const char *yyt3;const char *yyt4;const char *yyt5; } LexContext; typedef enum { @@ -142,11 +142,13 @@ parse_nodeid_body(UA_NodeId *id, const char *body, const char *end, Escaping esc } static UA_StatusCode -parse_nodeid(UA_NodeId *id, const char *pos, const char *end, Escaping esc) { +parse_nodeid(UA_NodeId *id, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping) { *id = UA_NODEID_NULL; /* Reset the NodeId */ LexContext context; memset(&context, 0, sizeof(LexContext)); - const char *ns = NULL, *nse= NULL; + UA_Byte *begin = (UA_Byte*)(uintptr_t)pos; + const char *ns = NULL, *nsu = NULL, *body = NULL; { @@ -184,26 +186,17 @@ yy4: } yy5: YYSKIP(); + nsu = context.yyt2; ns = context.yyt1; - nse = context.yyt2; - { - (void)pos; // Get rid of a dead store clang-analyzer warning - if(ns) { - UA_UInt32 tmp; - size_t len = (size_t)(nse - ns); - if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - id->namespaceIndex = (UA_UInt16)tmp; - } - - // From the current position until the end - return parse_nodeid_body(id, &pos[-2], end, esc); - } + YYSTAGP(body); + YYSHIFTSTAG(body, -2); + { goto match; } yy6: YYSKIP(); yych = YYPEEK(); switch (yych) { case '=': goto yy8; + case 'u': goto yy9; default: goto yy7; } yy7: @@ -224,10 +217,17 @@ yy8: case '8': case '9': YYSTAGP(context.yyt1); - goto yy9; + goto yy10; default: goto yy7; } yy9: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case '=': goto yy11; + default: goto yy7; + } +yy10: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -240,23 +240,46 @@ yy9: case '6': case '7': case '8': - case '9': goto yy9; + case '9': goto yy10; case ';': - YYSTAGP(context.yyt2); - goto yy10; + YYSTAGN(context.yyt2); + goto yy12; default: goto yy7; } -yy10: +yy11: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy7; + case ';': + YYSTAGN(context.yyt1); + YYSTAGP(context.yyt2); + goto yy12; + default: + YYSTAGP(context.yyt2); + goto yy13; + } +yy12: YYSKIP(); yych = YYPEEK(); switch (yych) { case 'b': case 'g': case 'i': - case 's': goto yy11; + case 's': goto yy14; default: goto yy7; } -yy11: +yy13: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy7; + case ';': + YYSTAGN(context.yyt1); + goto yy12; + default: goto yy13; + } +yy14: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -265,24 +288,58 @@ yy11: } } + + match: + if(nsu) { + /* NamespaceUri */ + UA_String nsUri = {(size_t)(body - 1 - nsu), (UA_Byte*)(uintptr_t)nsu}; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + if(nsMapping) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, + &id->namespaceIndex); + if(res != UA_STATUSCODE_GOOD) { + UA_String total = {(size_t)((const UA_Byte*)end - begin), begin}; + id->identifierType = UA_NODEIDTYPE_STRING; + return UA_String_copy(&total, &id->identifier.string); + } + } else if(ns) { + /* NamespaceIndex */ + UA_UInt32 tmp; + size_t len = (size_t)(body - 1 - ns); + if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + id->namespaceIndex = (UA_UInt16)tmp; + } + + /* From the current position until the end */ + return parse_nodeid_body(id, body, end, esc); } UA_StatusCode -UA_NodeId_parse(UA_NodeId *id, const UA_String str) { - UA_StatusCode res = - parse_nodeid(id, (const char *)str.data, - (const char*)str.data+str.length, ESCAPING_NONE); +UA_NodeId_parseEx(UA_NodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping) { + UA_StatusCode res = parse_nodeid(id, (const char *)str.data, + (const char*)str.data+str.length, + ESCAPING_NONE, nsMapping); if(res != UA_STATUSCODE_GOOD) UA_NodeId_clear(id); return res; } +UA_StatusCode +UA_NodeId_parse(UA_NodeId *id, const UA_String str) { + return UA_NodeId_parseEx(id, str, NULL); +} + static UA_StatusCode -parse_expandednodeid(UA_ExpandedNodeId *id, const char *pos, const char *end, Escaping esc) { +parse_expandednodeid(UA_ExpandedNodeId *id, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris) { *id = UA_EXPANDEDNODEID_NULL; /* Reset the NodeId */ LexContext context; memset(&context, 0, sizeof(LexContext)); - const char *svr = NULL, *svre = NULL, *nsu = NULL, *ns = NULL, *body = NULL; + const char *svr = NULL, *sve = NULL, *svu = NULL, + *nsu = NULL, *ns = NULL, *body = NULL, *begin = pos; { @@ -296,131 +353,78 @@ parse_expandednodeid(UA_ExpandedNodeId *id, const char *pos, const char *end, Es YYSTAGN(context.yyt2); YYSTAGN(context.yyt3); YYSTAGN(context.yyt4); - goto yy15; + YYSTAGN(context.yyt5); + goto yy18; case 'n': YYSTAGN(context.yyt1); YYSTAGN(context.yyt2); - goto yy16; + YYSTAGN(context.yyt5); + goto yy19; case 's': YYSTAGN(context.yyt1); YYSTAGN(context.yyt2); YYSTAGN(context.yyt3); YYSTAGN(context.yyt4); - goto yy17; - default: goto yy13; - } -yy13: - YYSKIP(); -yy14: - { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } -yy15: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case '=': goto yy18; - default: goto yy14; + YYSTAGN(context.yyt5); + goto yy20; + default: goto yy16; } yy16: YYSKIP(); - YYBACKUP(); - yych = YYPEEK(); - switch (yych) { - case 's': goto yy19; - default: goto yy14; - } yy17: - YYSKIP(); - YYBACKUP(); - yych = YYPEEK(); - switch (yych) { - case '=': goto yy18; - case 'v': goto yy21; - default: goto yy14; - } + { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } yy18: YYSKIP(); - svr = context.yyt1; - svre = context.yyt2; + yych = YYPEEK(); + switch (yych) { + case '=': goto yy21; + default: goto yy17; + } +yy19: + YYSKIP(); + YYBACKUP(); + yych = YYPEEK(); + switch (yych) { + case 's': goto yy22; + default: goto yy17; + } +yy20: + YYSKIP(); + YYBACKUP(); + yych = YYPEEK(); + switch (yych) { + case '=': goto yy21; + case 'v': goto yy24; + default: goto yy17; + } +yy21: + YYSKIP(); + svr = context.yyt5; + svu = context.yyt1; + sve = context.yyt2; ns = context.yyt3; nsu = context.yyt4; YYSTAGP(body); YYSHIFTSTAG(body, -2); - { - (void)pos; // Get rid of a dead store clang-analyzer warning - if(svr) { - size_t len = (size_t)((svre) - svr); - if(UA_readNumber((const UA_Byte*)svr, len, &id->serverIndex) != len) - return UA_STATUSCODE_BADDECODINGERROR; - } - - if(nsu) { - size_t len = (size_t)((body-1) - nsu); - UA_String nsuri; - nsuri.data = (UA_Byte*)(uintptr_t)nsu; - nsuri.length = len; - UA_StatusCode res = UA_String_copy(&nsuri, &id->namespaceUri); - if(res != UA_STATUSCODE_GOOD) - return res; - } else if(ns) { - UA_UInt32 tmp; - size_t len = (size_t)((body-1) - ns); - if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - id->nodeId.namespaceIndex = (UA_UInt16)tmp; - } - - // From the current position until the end - return parse_nodeid_body(&id->nodeId, &pos[-2], end, esc); - } -yy19: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case '=': goto yy22; - case 'u': goto yy23; - default: goto yy20; - } -yy20: - YYRESTORE(); - goto yy14; -yy21: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case 'r': goto yy24; - default: goto yy20; - } + { goto match; } yy22: YYSKIP(); yych = YYPEEK(); switch (yych) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - YYSTAGP(context.yyt3); - goto yy25; - default: goto yy20; + case '=': goto yy25; + case 'u': goto yy26; + default: goto yy23; } yy23: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case '=': goto yy26; - default: goto yy20; - } + YYRESTORE(); + goto yy17; yy24: YYSKIP(); yych = YYPEEK(); switch (yych) { - case '=': goto yy27; - default: goto yy20; + case 'r': goto yy27; + case 'u': goto yy28; + default: goto yy23; } yy25: YYSKIP(); @@ -435,24 +439,33 @@ yy25: case '6': case '7': case '8': - case '9': goto yy25; - case ';': goto yy28; - default: goto yy20; + case '9': + YYSTAGP(context.yyt3); + goto yy29; + default: goto yy23; } yy26: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: - case '\n': goto yy20; - case ';': - YYSTAGP(context.yyt4); - goto yy30; - default: - YYSTAGP(context.yyt4); - goto yy29; + case '=': goto yy30; + default: goto yy23; } yy27: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case '=': goto yy31; + default: goto yy23; + } +yy28: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case '=': goto yy32; + default: goto yy23; + } +yy29: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -465,43 +478,24 @@ yy27: case '6': case '7': case '8': - case '9': - YYSTAGP(context.yyt1); - goto yy31; - default: goto yy20; - } -yy28: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case 'b': - case 'g': - case 'i': - case 's': + case '9': goto yy29; + case ';': YYSTAGN(context.yyt4); - goto yy32; - default: goto yy20; - } -yy29: - YYSKIP(); - yych = YYPEEK(); - switch (yych) { - case 0x00: - case '\n': goto yy20; - case ';': goto yy30; - default: goto yy29; + goto yy33; + default: goto yy23; } yy30: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 'b': - case 'g': - case 'i': - case 's': + case 0x00: goto yy23; + case ';': YYSTAGN(context.yyt3); - goto yy32; - default: goto yy20; + YYSTAGP(context.yyt4); + goto yy33; + default: + YYSTAGP(context.yyt4); + goto yy34; } yy31: YYSKIP(); @@ -516,20 +510,77 @@ yy31: case '6': case '7': case '8': - case '9': goto yy31; - case ';': - YYSTAGP(context.yyt2); - goto yy33; - default: goto yy20; + case '9': + YYSTAGP(context.yyt5); + goto yy35; + default: goto yy23; } yy32: YYSKIP(); yych = YYPEEK(); switch (yych) { - case '=': goto yy18; - default: goto yy20; + case 0x00: goto yy23; + case ';': + YYSTAGP(context.yyt1); + YYSTAGP(context.yyt2); + YYSTAGN(context.yyt5); + goto yy37; + default: + YYSTAGP(context.yyt1); + goto yy36; } yy33: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 'b': + case 'g': + case 'i': + case 's': goto yy38; + default: goto yy23; + } +yy34: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy23; + case ';': + YYSTAGN(context.yyt3); + goto yy33; + default: goto yy34; + } +yy35: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy35; + case ';': + YYSTAGN(context.yyt1); + YYSTAGP(context.yyt2); + goto yy37; + default: goto yy23; + } +yy36: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy23; + case ';': + YYSTAGP(context.yyt2); + YYSTAGN(context.yyt5); + goto yy37; + default: goto yy36; + } +yy37: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -539,29 +590,92 @@ yy33: case 's': YYSTAGN(context.yyt3); YYSTAGN(context.yyt4); - goto yy32; - case 'n': goto yy34; - default: goto yy20; + goto yy38; + case 'n': goto yy39; + default: goto yy23; } -yy34: +yy38: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 's': goto yy19; - default: goto yy20; + case '=': goto yy21; + default: goto yy23; + } +yy39: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 's': goto yy22; + default: goto yy23; } } + + match: + if(svu) { + /* ServerUri */ + UA_String serverUri = {(size_t)(sve - svu), (UA_Byte*)(uintptr_t)svu}; + size_t i = 0; + for(; i < serverUrisSize; i++) { + if(UA_String_equal(&serverUri, &serverUris[i])) + break; + } + if(i == serverUrisSize) { + /* The ServerUri cannot be mapped. Return the entire input as a + * string NodeId. */ + UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; + id->nodeId.identifierType = UA_NODEIDTYPE_STRING; + return UA_String_copy(&total, &id->nodeId.identifier.string); + } + id->serverIndex = (UA_UInt32)i; + } else if(svr) { + /* ServerIndex */ + size_t len = (size_t)(sve - svr); + if(UA_readNumber((const UA_Byte*)svr, len, &id->serverIndex) != len) + return UA_STATUSCODE_BADDECODINGERROR; + } + + if(nsu) { + /* NamespaceUri */ + UA_String nsuri = {(size_t)(body - 1 - nsu), (UA_Byte*)(uintptr_t)nsu}; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + /* Don't try to map the NamespaceUri for ServerIndex != 0 */ + if(nsMapping && id->serverIndex == 0) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsuri, + &id->nodeId.namespaceIndex); + if(res != UA_STATUSCODE_GOOD) + res = UA_String_copy(&nsuri, &id->namespaceUri); /* Keep the Uri without mapping */ + if(res != UA_STATUSCODE_GOOD) + return res; + } else if(ns) { + /* NamespaceIndex */ + UA_UInt32 tmp; + size_t len = (size_t)(body - 1 - ns); + if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + id->nodeId.namespaceIndex = (UA_UInt16)tmp; + } + + /* From the current position until the end */ + return parse_nodeid_body(&id->nodeId, body, end, esc); +} + +UA_StatusCode +UA_ExpandedNodeId_parseEx(UA_ExpandedNodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris) { + UA_StatusCode res = + parse_expandednodeid(id, (const char *)str.data, + (const char *)str.data + str.length, ESCAPING_NONE, + nsMapping, serverUrisSize, serverUris); + if(res != UA_STATUSCODE_GOOD) + UA_ExpandedNodeId_clear(id); + return res; } UA_StatusCode UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str) { - UA_StatusCode res = - parse_expandednodeid(id, (const char *)str.data, - (const char *)str.data + str.length, ESCAPING_NONE); - if(res != UA_STATUSCODE_GOOD) - UA_ExpandedNodeId_clear(id); - return res; + return UA_ExpandedNodeId_parseEx(id, str, NULL, 0, NULL); } static UA_StatusCode @@ -651,70 +765,67 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, unsigned int yyaccept = 0; yych = YYPEEK(); switch (yych) { - case '.': goto yy38; - case '/': goto yy39; - case '<': goto yy40; - default: goto yy36; + case '.': goto yy43; + case '/': goto yy44; + case '<': goto yy45; + default: goto yy41; } -yy36: +yy41: YYSKIP(); -yy37: +yy42: { *ppos = pos-1; return UA_STATUSCODE_GOOD; } -yy38: +yy43: YYSKIP(); { current.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES); goto reftype_target; } -yy39: +yy44: YYSKIP(); { current.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES); goto reftype_target; } -yy40: +yy45: yyaccept = 0; YYSKIP(); YYBACKUP(); yych = YYPEEK(); switch (yych) { case 0x00: - case '>': goto yy37; + case '>': goto yy42; case '&': YYSTAGP(context.yyt1); - goto yy43; + goto yy48; default: YYSTAGP(context.yyt1); - goto yy41; + goto yy46; } -yy41: +yy46: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy42; - case '&': goto yy43; - case '>': goto yy44; - default: goto yy41; + case 0x00: goto yy47; + case '&': goto yy48; + case '>': goto yy49; + default: goto yy46; } -yy42: +yy47: YYRESTORE(); - if (yyaccept == 0) { - goto yy37; - } else { - goto yy45; - } -yy43: + if (yyaccept == 0) goto yy42; + else goto yy50; +yy48: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy42; - case '&': goto yy43; - case '>': goto yy46; - default: goto yy41; + case 0x00: goto yy47; + case '&': goto yy48; + case '>': goto yy51; + default: goto yy46; } -yy44: +yy49: YYSKIP(); -yy45: +yy50: begin = context.yyt1; YYSTAGP(finish); YYSHIFTSTAG(finish, -1); @@ -731,7 +842,7 @@ yy45: } // Try to parse a NodeId for the ReferenceType (non-standard!) - res = parse_nodeid(¤t.referenceTypeId, begin, finish, esc); + res = parse_nodeid(¤t.referenceTypeId, begin, finish, esc, NULL); if(res == UA_STATUSCODE_GOOD) goto reftype_target; @@ -742,16 +853,16 @@ yy45: UA_QualifiedName_clear(&refqn); goto reftype_target; } -yy46: +yy51: yyaccept = 1; YYSKIP(); YYBACKUP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy45; - case '&': goto yy43; - case '>': goto yy44; - default: goto yy41; + case 0x00: goto yy50; + case '&': goto yy48; + case '>': goto yy49; + default: goto yy46; } } @@ -771,18 +882,18 @@ yy46: case '.': case '/': case '<': - case '[': goto yy48; + case '[': goto yy53; case '&': YYSTAGP(context.yyt1); - goto yy51; + goto yy56; default: YYSTAGP(context.yyt1); - goto yy49; + goto yy54; } -yy48: +yy53: YYSKIP(); { pos--; goto add_element; } -yy49: +yy54: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -791,23 +902,23 @@ yy49: case '.': case '/': case '<': - case '[': goto yy50; - case '&': goto yy51; - default: goto yy49; + case '[': goto yy55; + case '&': goto yy56; + default: goto yy54; } -yy50: +yy55: begin = context.yyt1; { res = parse_refpath_qn(¤t.targetName, begin, pos, esc); goto add_element; } -yy51: +yy56: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy50; - case '&': goto yy51; - default: goto yy49; + case 0x00: goto yy55; + case '&': goto yy56; + default: goto yy54; } } @@ -862,7 +973,7 @@ parseAttributeOperand(UA_AttributeOperand *ao, const UA_String str, UA_NodeId de if(*pos != '/' && *pos != '.' && *pos != '<' && *pos != '#' && *pos != '[') { const char *id_pos = pos; pos = find_unescaped((char*)(uintptr_t)pos, end, true); - res = parse_nodeid(&ao->nodeId, id_pos, pos, ESCAPING_AND_EXTENDED); + res = parse_nodeid(&ao->nodeId, id_pos, pos, ESCAPING_AND_EXTENDED, NULL); if(res != UA_STATUSCODE_GOOD) goto cleanup; } diff --git a/src/util/ua_types_lex.re b/src/util/ua_types_lex.re index 68bbb3f13..56a1b75c3 100644 --- a/src/util/ua_types_lex.re +++ b/src/util/ua_types_lex.re @@ -54,6 +54,7 @@ typedef enum { re2c:flags:input = custom; nodeid_body = ("i=" | "s=" | "g=" | "b="); + escaped_uri = [^;\000]*; */ static UA_StatusCode @@ -149,91 +150,142 @@ parse_nodeid_body(UA_NodeId *id, const char *body, const char *end, Escaping esc } static UA_StatusCode -parse_nodeid(UA_NodeId *id, const char *pos, const char *end, Escaping esc) { +parse_nodeid(UA_NodeId *id, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping) { *id = UA_NODEID_NULL; /* Reset the NodeId */ LexContext context; memset(&context, 0, sizeof(LexContext)); - const char *ns = NULL, *nse= NULL; + UA_Byte *begin = (UA_Byte*)(uintptr_t)pos; + const char *ns = NULL, *nsu = NULL, *body = NULL; - /*!re2c - ("ns=" @ns [0-9]+ @nse ";")? nodeid_body { - (void)pos; // Get rid of a dead store clang-analyzer warning - if(ns) { - UA_UInt32 tmp; - size_t len = (size_t)(nse - ns); - if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - id->namespaceIndex = (UA_UInt16)tmp; + /*!re2c // Match the grammar + (("nsu=" @nsu escaped_uri | "ns=" @ns [0-9]+) ";")? + @body nodeid_body { goto match; } + * { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } */ + + match: + if(nsu) { + /* NamespaceUri */ + UA_String nsUri = {(size_t)(body - 1 - nsu), (UA_Byte*)(uintptr_t)nsu}; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + if(nsMapping) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, + &id->namespaceIndex); + if(res != UA_STATUSCODE_GOOD) { + UA_String total = {(size_t)((const UA_Byte*)end - begin), begin}; + id->identifierType = UA_NODEIDTYPE_STRING; + return UA_String_copy(&total, &id->identifier.string); } - - // From the current position until the end - return parse_nodeid_body(id, &pos[-2], end, esc); + } else if(ns) { + /* NamespaceIndex */ + UA_UInt32 tmp; + size_t len = (size_t)(body - 1 - ns); + if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + id->namespaceIndex = (UA_UInt16)tmp; } - * { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } */ + /* From the current position until the end */ + return parse_nodeid_body(id, body, end, esc); } UA_StatusCode -UA_NodeId_parse(UA_NodeId *id, const UA_String str) { - UA_StatusCode res = - parse_nodeid(id, (const char *)str.data, - (const char*)str.data+str.length, ESCAPING_NONE); +UA_NodeId_parseEx(UA_NodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping) { + UA_StatusCode res = parse_nodeid(id, (const char *)str.data, + (const char*)str.data+str.length, + ESCAPING_NONE, nsMapping); if(res != UA_STATUSCODE_GOOD) UA_NodeId_clear(id); return res; } +UA_StatusCode +UA_NodeId_parse(UA_NodeId *id, const UA_String str) { + return UA_NodeId_parseEx(id, str, NULL); +} + static UA_StatusCode -parse_expandednodeid(UA_ExpandedNodeId *id, const char *pos, const char *end, Escaping esc) { +parse_expandednodeid(UA_ExpandedNodeId *id, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris) { *id = UA_EXPANDEDNODEID_NULL; /* Reset the NodeId */ LexContext context; memset(&context, 0, sizeof(LexContext)); - const char *svr = NULL, *svre = NULL, *nsu = NULL, *ns = NULL, *body = NULL; + const char *svr = NULL, *sve = NULL, *svu = NULL, + *nsu = NULL, *ns = NULL, *body = NULL, *begin = pos; - /*!re2c - // The "." character class also matches \x00. Exlude this as we produce - // an infinite sequence of zeros once the end of the buffer was hit. - ("svr=" @svr [0-9]+ @svre ";")? - ("ns=" @ns [0-9]+ ";" | "nsu=" @nsu (.\[;\x00])* ";")? - @body nodeid_body { - (void)pos; // Get rid of a dead store clang-analyzer warning - if(svr) { - size_t len = (size_t)((svre) - svr); - if(UA_readNumber((const UA_Byte*)svr, len, &id->serverIndex) != len) - return UA_STATUSCODE_BADDECODINGERROR; + /*!re2c // Match the grammar + (("svr=" @svr [0-9]+ | "svu=" @svu escaped_uri) @sve ";")? + (("ns=" @ns [0-9]+ | "nsu=" @nsu [^;\x00]*) ";")? + @body nodeid_body { goto match; } + * { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } */ + + match: + if(svu) { + /* ServerUri */ + UA_String serverUri = {(size_t)(sve - svu), (UA_Byte*)(uintptr_t)svu}; + size_t i = 0; + for(; i < serverUrisSize; i++) { + if(UA_String_equal(&serverUri, &serverUris[i])) + break; } - - if(nsu) { - size_t len = (size_t)((body-1) - nsu); - UA_String nsuri; - nsuri.data = (UA_Byte*)(uintptr_t)nsu; - nsuri.length = len; - UA_StatusCode res = UA_String_copy(&nsuri, &id->namespaceUri); - if(res != UA_STATUSCODE_GOOD) - return res; - } else if(ns) { - UA_UInt32 tmp; - size_t len = (size_t)((body-1) - ns); - if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - id->nodeId.namespaceIndex = (UA_UInt16)tmp; + if(i == serverUrisSize) { + /* The ServerUri cannot be mapped. Return the entire input as a + * string NodeId. */ + UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; + id->nodeId.identifierType = UA_NODEIDTYPE_STRING; + return UA_String_copy(&total, &id->nodeId.identifier.string); } - - // From the current position until the end - return parse_nodeid_body(&id->nodeId, &pos[-2], end, esc); + id->serverIndex = (UA_UInt32)i; + } else if(svr) { + /* ServerIndex */ + size_t len = (size_t)(sve - svr); + if(UA_readNumber((const UA_Byte*)svr, len, &id->serverIndex) != len) + return UA_STATUSCODE_BADDECODINGERROR; } - * { (void)pos; return UA_STATUSCODE_BADDECODINGERROR; } */ + if(nsu) { + /* NamespaceUri */ + UA_String nsuri = {(size_t)(body - 1 - nsu), (UA_Byte*)(uintptr_t)nsu}; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + /* Don't try to map the NamespaceUri for ServerIndex != 0 */ + if(nsMapping && id->serverIndex == 0) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsuri, + &id->nodeId.namespaceIndex); + if(res != UA_STATUSCODE_GOOD) + res = UA_String_copy(&nsuri, &id->namespaceUri); /* Keep the Uri without mapping */ + if(res != UA_STATUSCODE_GOOD) + return res; + } else if(ns) { + /* NamespaceIndex */ + UA_UInt32 tmp; + size_t len = (size_t)(body - 1 - ns); + if(UA_readNumber((const UA_Byte*)ns, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + id->nodeId.namespaceIndex = (UA_UInt16)tmp; + } + + /* From the current position until the end */ + return parse_nodeid_body(&id->nodeId, body, end, esc); +} + +UA_StatusCode +UA_ExpandedNodeId_parseEx(UA_ExpandedNodeId *id, const UA_String str, + const UA_NamespaceMapping *nsMapping, + size_t serverUrisSize, const UA_String *serverUris) { + UA_StatusCode res = + parse_expandednodeid(id, (const char *)str.data, + (const char *)str.data + str.length, ESCAPING_NONE, + nsMapping, serverUrisSize, serverUris); + if(res != UA_STATUSCODE_GOOD) + UA_ExpandedNodeId_clear(id); + return res; } UA_StatusCode UA_ExpandedNodeId_parse(UA_ExpandedNodeId *id, const UA_String str) { - UA_StatusCode res = - parse_expandednodeid(id, (const char *)str.data, - (const char *)str.data + str.length, ESCAPING_NONE); - if(res != UA_STATUSCODE_GOOD) - UA_ExpandedNodeId_clear(id); - return res; + return UA_ExpandedNodeId_parseEx(id, str, NULL, 0, NULL); } static UA_StatusCode @@ -342,7 +394,7 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, } // Try to parse a NodeId for the ReferenceType (non-standard!) - res = parse_nodeid(¤t.referenceTypeId, begin, finish, esc); + res = parse_nodeid(¤t.referenceTypeId, begin, finish, esc, NULL); if(res == UA_STATUSCODE_GOOD) goto reftype_target; @@ -364,7 +416,7 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, return res; /*!re2c - @begin ([^nodeId, id_pos, pos, ESCAPING_AND_EXTENDED); + res = parse_nodeid(&ao->nodeId, id_pos, pos, ESCAPING_AND_EXTENDED, NULL); if(res != UA_STATUSCODE_GOOD) goto cleanup; } diff --git a/tests/check_utils.c b/tests/check_utils.c index 5e4cde2fb..30b81f2bf 100644 --- a/tests/check_utils.c +++ b/tests/check_utils.c @@ -447,12 +447,14 @@ START_TEST(idToStringWithMapping) { nsMapping.namespaceUris = namespaces; nsMapping.namespaceUrisSize = 2; - UA_NodeId n; + UA_NodeId n, n2; UA_String str = UA_STRING_NULL; n = UA_NODEID_NUMERIC(1,1234567890); UA_NodeId_printEx(&n, &str, &nsMapping); assertNodeIdString(&str, "nsu=ns2;i=1234567890"); + UA_NodeId_parseEx(&n2, str, &nsMapping); + ck_assert(UA_NodeId_equal(&n, &n2)); UA_String_clear(&str); n = UA_NODEID_NUMERIC(0xFFFF,0xFFFFFFFF); @@ -618,7 +620,7 @@ START_TEST(expIdToStringNumericWithMapping) { UA_STRING_STATIC("uri:server2") }; - UA_ExpandedNodeId n; + UA_ExpandedNodeId n, n2; UA_String str = UA_STRING_NULL; n = UA_EXPANDEDNODEID_NUMERIC(0,0); @@ -630,6 +632,18 @@ START_TEST(expIdToStringNumericWithMapping) { n.namespaceUri = UA_STRING("testuri"); UA_ExpandedNodeId_printEx(&n, &str, NULL, 2, serverUris); assertNodeIdString(&str, "svu=uri:server2;nsu=testuri;i=0"); + UA_ExpandedNodeId_parseEx(&n2, str, NULL, 2, serverUris); + ck_assert(UA_ExpandedNodeId_equal(&n, &n2)); + UA_ExpandedNodeId_clear(&n2); + UA_String_clear(&str); + + n.namespaceUri = UA_STRING_NULL; + n.nodeId.namespaceIndex = 2; + UA_ExpandedNodeId_printEx(&n, &str, NULL, 0, NULL); + assertNodeIdString(&str, "svr=1;ns=2;i=0"); + UA_ExpandedNodeId_parseEx(&n2, str, NULL, 0, NULL); + ck_assert(UA_ExpandedNodeId_equal(&n, &n2)); + UA_ExpandedNodeId_clear(&n2); UA_String_clear(&str); } END_TEST From 708aec9150b39290ac247b2939cba28493fb0a0a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 25 Dec 2024 15:16:54 +0100 Subject: [PATCH 096/158] refactor(core): For JSON encode NodeIds as string (v1.05 spec) --- src/ua_types_encoding_json.c | 363 ++----------------------------- tests/check_types_builtin_json.c | 148 ++++--------- 2 files changed, 68 insertions(+), 443 deletions(-) diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index b5f8983c0..d593fe64f 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -185,14 +185,6 @@ static const char* UA_JSONKEY_TEXT = "Text"; static const char* UA_JSONKEY_NAME = "Name"; static const char* UA_JSONKEY_URI = "Uri"; -/* NodeId */ -static const char* UA_JSONKEY_ID = "Id"; -static const char* UA_JSONKEY_IDTYPE = "IdType"; -static const char* UA_JSONKEY_NAMESPACE = "Namespace"; - -/* ExpandedNodeId */ -static const char* UA_JSONKEY_SERVERURI = "ServerUri"; - /* Variant */ static const char* UA_JSONKEY_TYPE = "Type"; static const char* UA_JSONKEY_BODY = "Body"; @@ -627,166 +619,22 @@ ENCODE_JSON(DateTime) { } /* NodeId */ -static status -NodeId_encodeJsonInternal(CtxJson *ctx, UA_NodeId const *src) { - status ret = UA_STATUSCODE_GOOD; - switch(src->identifierType) { - case UA_NODEIDTYPE_NUMERIC: - ret |= writeJsonKey(ctx, UA_JSONKEY_ID); - ret |= ENCODE_DIRECT_JSON(&src->identifier.numeric, UInt32); - break; - case UA_NODEIDTYPE_STRING: - ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); - ret |= writeChar(ctx, '1'); - ret |= writeJsonKey(ctx, UA_JSONKEY_ID); - ret |= ENCODE_DIRECT_JSON(&src->identifier.string, String); - break; - case UA_NODEIDTYPE_GUID: - ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); - ret |= writeChar(ctx, '2'); - ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ - ret |= ENCODE_DIRECT_JSON(&src->identifier.guid, Guid); - break; - case UA_NODEIDTYPE_BYTESTRING: - ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); - ret |= writeChar(ctx, '3'); - ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ - ret |= ENCODE_DIRECT_JSON(&src->identifier.byteString, ByteString); - break; - default: - return UA_STATUSCODE_BADINTERNALERROR; - } - return ret; -} - ENCODE_JSON(NodeId) { - /* Encode as string (non-standard). Encode with the standard utf8 escaping. - * As the NodeId can contain quote characters, etc. */ - UA_StatusCode ret = UA_STATUSCODE_GOOD; - if(ctx->stringNodeIds) { - UA_String out = UA_STRING_NULL; - ret |= UA_NodeId_print(src, &out); - ret |= ENCODE_DIRECT_JSON(&out, String); - UA_String_clear(&out); - return ret; - } - - /* Encode as object */ - ret |= writeJsonObjStart(ctx); - ret |= NodeId_encodeJsonInternal(ctx, src); - if(ctx->useReversible) { - if(src->namespaceIndex > 0) { - ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); - } - } else { - /* For the non-reversible encoding, the field is the NamespaceUri - * associated with the NamespaceIndex, encoded as a JSON string. - * A NamespaceIndex of 1 is always encoded as a JSON number. */ - ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); - if(src->namespaceIndex == 1) { - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); - } else { - /* Check if Namespace given and in range */ - UA_String nsUri = UA_STRING_NULL; - UA_UInt16 ns = src->namespaceIndex; - if(ctx->namespaceMapping) - UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); - if(nsUri.length > 0) { - ret |= ENCODE_DIRECT_JSON(&nsUri, String); - } else { - /* If not found, print the identifier */ - ret |= ENCODE_DIRECT_JSON(&ns, UInt16); - } - } - } - - return ret | writeJsonObjEnd(ctx); + UA_String out = UA_STRING_NULL; + UA_StatusCode ret = UA_NodeId_printEx(src, &out, ctx->namespaceMapping); + ret |= ENCODE_DIRECT_JSON(&out, String); + UA_String_clear(&out); + return ret; } /* ExpandedNodeId */ ENCODE_JSON(ExpandedNodeId) { - /* Encode as string (non-standard). Encode with utf8 escaping as the NodeId - * can contain quote characters, etc. */ - UA_StatusCode ret = UA_STATUSCODE_GOOD; - if(ctx->stringNodeIds) { - UA_String out = UA_STRING_NULL; - ret |= UA_ExpandedNodeId_print(src, &out); - ret |= ENCODE_DIRECT_JSON(&out, String); - UA_String_clear(&out); - return ret; - } - - /* Encode as object */ - ret |= writeJsonObjStart(ctx); - - /* Encode the identifier portion */ - ret |= NodeId_encodeJsonInternal(ctx, &src->nodeId); - - if(ctx->useReversible) { - /* Reversible Case */ - - if(src->namespaceUri.data) { - /* If the NamespaceUri is specified it is encoded as a JSON string - * in this field */ - ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); - ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); - } else if(src->nodeId.namespaceIndex > 0) { - /* If the NamespaceUri is not specified, the NamespaceIndex is - * encoded. Encoded as a JSON number for the reversible encoding. - * Omitted if the NamespaceIndex equals 0. */ - ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); - ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); - } - - /* Encode the serverIndex/Url. As a JSON number for the reversible - * encoding. Omitted if the ServerIndex equals 0. */ - if(src->serverIndex > 0) { - ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); - ret |= ENCODE_DIRECT_JSON(&src->serverIndex, UInt32); - } - } else { - /* Non-Reversible Case */ - - /* If the NamespaceUri is not specified, the NamespaceIndex is encoded - * with these rules: For the non-reversible encoding the field is the - * NamespaceUri associated with the NamespaceIndex encoded as a JSON - * string. A NamespaceIndex of 1 is always encoded as a JSON number. */ - - ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); - if(src->namespaceUri.data) { - ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); - } else { - if(src->nodeId.namespaceIndex == 1) { - ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); - } else { - /* Check if Namespace given and in range */ - UA_String nsUri = UA_STRING_NULL; - UA_UInt16 ns = src->nodeId.namespaceIndex; - if(ctx->namespaceMapping) - UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); - if(nsUri.length > 0) { - ret |= ENCODE_DIRECT_JSON(&nsUri, String); - } else { - ret |= ENCODE_DIRECT_JSON(&ns, UInt16); - } - } - } - - /* For the non-reversible encoding, this field is the ServerUri - * associated with the ServerIndex portion of the ExpandedNodeId, - * encoded as a JSON string. */ - - /* Check if server given and in range */ - if(src->serverIndex >= ctx->serverUrisSize || !ctx->serverUris) - return UA_STATUSCODE_BADNOTFOUND; - - UA_String serverUriEntry = ctx->serverUris[src->serverIndex]; - ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); - ret |= ENCODE_DIRECT_JSON(&serverUriEntry, String); - } - - return ret | writeJsonObjEnd(ctx); + UA_String out = UA_STRING_NULL; + UA_StatusCode ret = UA_ExpandedNodeId_printEx(src, &out, ctx->namespaceMapping, + ctx->serverUrisSize, ctx->serverUris); + ret |= ENCODE_DIRECT_JSON(&out, String); + UA_String_clear(&out); + return ret; } /* LocalizedText */ @@ -1744,188 +1592,25 @@ lookAheadForKey(ParseCtx *ctx, const char *key, size_t *resultIndex) { return ret; } -static status -prepareDecodeNodeIdJson(ParseCtx *ctx, UA_NodeId *dst, - u8 *fieldCount, DecodeEntry *entries) { - UA_assert(currentTokenType(ctx) == CJ5_TOKEN_OBJECT); - - /* possible keys: Id, IdType, NamespaceIndex */ - /* Id must always be present */ - entries[*fieldCount].fieldName = UA_JSONKEY_ID; - entries[*fieldCount].found = false; - entries[*fieldCount].type = NULL; - entries[*fieldCount].function = NULL; - - /* IdType */ - size_t idIndex = 0; - status ret = lookAheadForKey(ctx, UA_JSONKEY_IDTYPE, &idIndex); - if(ret == UA_STATUSCODE_GOOD) { - size_t size = getTokenLength(&ctx->tokens[idIndex]); - if(size < 1) - return UA_STATUSCODE_BADDECODINGERROR; - - const char *idType = &ctx->json5[ctx->tokens[idIndex].start]; - - if(idType[0] == '2') { - dst->identifierType = UA_NODEIDTYPE_GUID; - entries[*fieldCount].fieldPointer = &dst->identifier.guid; - entries[*fieldCount].type = &UA_TYPES[UA_TYPES_GUID]; - } else if(idType[0] == '1') { - dst->identifierType = UA_NODEIDTYPE_STRING; - entries[*fieldCount].fieldPointer = &dst->identifier.string; - entries[*fieldCount].type = &UA_TYPES[UA_TYPES_STRING]; - } else if(idType[0] == '3') { - dst->identifierType = UA_NODEIDTYPE_BYTESTRING; - entries[*fieldCount].fieldPointer = &dst->identifier.byteString; - entries[*fieldCount].type = &UA_TYPES[UA_TYPES_BYTESTRING]; - } else { - return UA_STATUSCODE_BADDECODINGERROR; - } - - /* Id always present */ - (*fieldCount)++; - - entries[*fieldCount].fieldName = UA_JSONKEY_IDTYPE; - entries[*fieldCount].fieldPointer = NULL; - entries[*fieldCount].function = NULL; - entries[*fieldCount].found = false; - entries[*fieldCount].type = NULL; - - /* IdType */ - (*fieldCount)++; - } else { - dst->identifierType = UA_NODEIDTYPE_NUMERIC; - entries[*fieldCount].fieldPointer = &dst->identifier.numeric; - entries[*fieldCount].function = NULL; - entries[*fieldCount].found = false; - entries[*fieldCount].type = &UA_TYPES[UA_TYPES_UINT32]; - (*fieldCount)++; - } - - /* NodeId has a NamespaceIndex (the ExpandedNodeId specialization may - * overwrite this) */ - entries[*fieldCount].fieldName = UA_JSONKEY_NAMESPACE; - entries[*fieldCount].fieldPointer = &dst->namespaceIndex; - entries[*fieldCount].function = NULL; - entries[*fieldCount].found = false; - entries[*fieldCount].type = &UA_TYPES[UA_TYPES_UINT16]; - (*fieldCount)++; - - return UA_STATUSCODE_GOOD; -} - DECODE_JSON(NodeId) { - /* Non-standard decoding of NodeIds from the string representation */ - if(currentTokenType(ctx) == CJ5_TOKEN_STRING) { - GET_TOKEN; - UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; - ctx->index++; - return UA_NodeId_parse(dst, str); - } + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; - /* Object representation */ - CHECK_OBJECT; - - u8 fieldCount = 0; - DecodeEntry entries[3]; - status ret = prepareDecodeNodeIdJson(ctx, dst, &fieldCount, entries); - if(ret != UA_STATUSCODE_GOOD) - return ret; - return decodeFields(ctx, entries, fieldCount); -} - -static status -decodeExpandedNodeIdNamespace(ParseCtx *ctx, void *dst, const UA_DataType *type) { - UA_ExpandedNodeId *en = (UA_ExpandedNodeId*)dst; - - /* Parse as a number */ - size_t oldIndex = ctx->index; - status ret = UInt16_decodeJson(ctx, &en->nodeId.namespaceIndex, NULL); - if(ret == UA_STATUSCODE_GOOD) - return ret; - - /* Parse as a string */ - ctx->index = oldIndex; /* Reset the index */ - ret = String_decodeJson(ctx, &en->namespaceUri, NULL); - if(ret != UA_STATUSCODE_GOOD) - return ret; - - /* Replace with the index if the URI is found. Otherwise keep the string. */ - if(ctx->namespaceMapping) { - UA_StatusCode mapRes = - UA_NamespaceMapping_uri2Index(ctx->namespaceMapping, en->namespaceUri, - &en->nodeId.namespaceIndex); - if(mapRes == UA_STATUSCODE_GOOD) - UA_String_clear(&en->namespaceUri); - } - - return UA_STATUSCODE_GOOD; -} - -static status -decodeExpandedNodeIdServerUri(ParseCtx *ctx, void *dst, const UA_DataType *type) { - UA_ExpandedNodeId *en = (UA_ExpandedNodeId*)dst; - - /* Parse as a number */ - size_t oldIndex = ctx->index; - status ret = UInt32_decodeJson(ctx, &en->serverIndex, NULL); - if(ret == UA_STATUSCODE_GOOD) - return ret; - - /* Parse as a string */ - UA_String uri = UA_STRING_NULL; - ctx->index = oldIndex; /* Reset the index */ - ret = String_decodeJson(ctx, &uri, NULL); - if(ret != UA_STATUSCODE_GOOD) - return ret; - - /* Try to translate the URI into an index */ - ret = UA_STATUSCODE_BADDECODINGERROR; - for(size_t i = 0; i < ctx->serverUrisSize; i++) { - if(UA_String_equal(&uri, &ctx->serverUris[i])) { - en->serverIndex = (UA_UInt32)i; - ret = UA_STATUSCODE_GOOD; - break; - } - } - - UA_String_clear(&uri); - return ret; + ctx->index++; + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + return UA_NodeId_parseEx(dst, str, ctx->namespaceMapping); } DECODE_JSON(ExpandedNodeId) { - /* Non-standard decoding of ExpandedNodeIds from the string representation */ - if(currentTokenType(ctx) == CJ5_TOKEN_STRING) { - GET_TOKEN; - UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; - ctx->index++; - return UA_ExpandedNodeId_parse(dst, str); - } + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; - /* Object representation */ - CHECK_OBJECT; - - u8 fieldCount = 0; - DecodeEntry entries[4]; - status ret = prepareDecodeNodeIdJson(ctx, &dst->nodeId, &fieldCount, entries); - if(ret != UA_STATUSCODE_GOOD) - return ret; - - /* Overwrite the namespace entry */ - fieldCount--; - entries[fieldCount].fieldPointer = dst; - entries[fieldCount].function = decodeExpandedNodeIdNamespace; - entries[fieldCount].type = NULL; - fieldCount++; - - entries[fieldCount].fieldName = UA_JSONKEY_SERVERURI; - entries[fieldCount].fieldPointer = dst; - entries[fieldCount].function = decodeExpandedNodeIdServerUri; - entries[fieldCount].found = false; - entries[fieldCount].type = NULL; - fieldCount++; - - return decodeFields(ctx, entries, fieldCount); + ctx->index++; + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + return UA_ExpandedNodeId_parseEx(dst, str, ctx->namespaceMapping, + ctx->serverUrisSize, ctx->serverUris); } DECODE_JSON(DateTime) { diff --git a/tests/check_types_builtin_json.c b/tests/check_types_builtin_json.c index 576cd179c..da1d9f72c 100644 --- a/tests/check_types_builtin_json.c +++ b/tests/check_types_builtin_json.c @@ -1233,7 +1233,7 @@ START_TEST(UA_NodeId_Numeric_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Id\":5555}"; + char* result = "\"i=5555\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1254,7 +1254,7 @@ START_TEST(UA_NodeId_Numeric_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Id\":5555,\"Namespace\":4}"; + char* result = "\"ns=4;i=5555\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1276,7 +1276,7 @@ START_TEST(UA_NodeId_String_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"foobar\"}"; + char* result = "\"s=foobar\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1297,7 +1297,7 @@ START_TEST(UA_NodeId_String_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"foobar\",\"Namespace\":5}"; + char* result = "\"ns=5;s=foobar\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1321,7 +1321,7 @@ START_TEST(UA_NodeId_Guid_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":2,\"Id\":\"00000003-0009-000A-0807-060504030201\"}"; + char* result = "\"g=00000003-0009-000a-0807-060504030201\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1335,8 +1335,6 @@ START_TEST(UA_NodeId_Guid_Namespace_json_encode) { *src = UA_NODEID_GUID(5, g); const UA_DataType *type = &UA_TYPES[UA_TYPES_NODEID]; size_t size = UA_calcSizeJson((void *) src, type, NULL); - // {"IdType":2,"Id":"00000003-0009-000A-0807-060504030201","Namespace":5} - ck_assert_uint_eq(size, 70); UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); @@ -1345,7 +1343,7 @@ START_TEST(UA_NodeId_Guid_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":2,\"Id\":\"00000003-0009-000A-0807-060504030201\",\"Namespace\":5}"; + char* result = "\"ns=5;g=00000003-0009-000a-0807-060504030201\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1359,8 +1357,6 @@ START_TEST(UA_NodeId_ByteString_json_encode) { *src = UA_NODEID_BYTESTRING_ALLOC(0, "asdfasdf"); const UA_DataType *type = &UA_TYPES[UA_TYPES_NODEID]; size_t size = UA_calcSizeJson((void *) src, type, NULL); - //{"IdType":3,"Id":"YXNkZmFzZGY="} - ck_assert_uint_eq(size, 32); UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); @@ -1369,7 +1365,7 @@ START_TEST(UA_NodeId_ByteString_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":3,\"Id\":\"YXNkZmFzZGY=\"}"; + char* result = "\"b=YXNkZmFzZGY=\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1390,7 +1386,7 @@ START_TEST(UA_NodeId_ByteString_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":3,\"Id\":\"YXNkZmFzZGY=\",\"Namespace\":5}"; + char* result = "\"ns=5;b=YXNkZmFzZGY=\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1423,7 +1419,7 @@ START_TEST(UA_NodeId_NonReversible_Numeric_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Id\":5555,\"Namespace\":\"ns2\"}"; + char* result = "\"nsu=ns2;i=5555\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2072,7 +2068,7 @@ START_TEST(UA_Variant_NodeId_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":17,\"Body\":{\"IdType\":1,\"Id\":\"theID\",\"Namespace\":1}}"; + char* result = "{\"Type\":17,\"Body\":\"ns=1;s=theID\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2392,7 +2388,7 @@ START_TEST(UA_Variant_Matrix_NodeId_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[[[[{\"Id\":1,\"Namespace\":1}],[{\"Id\":2,\"Namespace\":1}]],[[{\"Id\":3,\"Namespace\":1}],[{\"Id\":4,\"Namespace\":1}]]],[[[{\"Id\":5,\"Namespace\":1}],[{\"Id\":6,\"Namespace\":1}]],[[{\"Id\":7,\"Namespace\":1}],[{\"Id\":8,\"Namespace\":1}]]]]}"; + char* result = "{\"Body\":[[[[\"ns=1;i=1\"],[\"ns=1;i=2\"]],[[\"ns=1;i=3\"],[\"ns=1;i=4\"]]],[[[\"ns=1;i=5\"],[\"ns=1;i=6\"]],[[\"ns=1;i=7\"],[\"ns=1;i=8\"]]]]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2400,8 +2396,6 @@ START_TEST(UA_Variant_Matrix_NodeId_NonReversible_json_encode) { } END_TEST - - START_TEST(UA_Variant_Wrap_json_encode) { UA_Variant *src = UA_Variant_new(); UA_Variant_init(src); @@ -2423,7 +2417,7 @@ START_TEST(UA_Variant_Wrap_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":22,\"Body\":{\"TypeId\":{\"Id\":511},\"Body\":{\"ViewId\":{\"Id\":99999},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}}}"; + char* result = "{\"Type\":22,\"Body\":{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2465,7 +2459,7 @@ START_TEST(UA_Variant_Wrap_Array_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":22,\"Body\":[{\"TypeId\":{\"Id\":511},\"Body\":{\"ViewId\":{\"Id\":1},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"TypeId\":{\"Id\":511},\"Body\":{\"ViewId\":{\"Id\":2},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; + char* result = "{\"Type\":22,\"Body\":[{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2507,7 +2501,7 @@ START_TEST(UA_Variant_Wrap_Array_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[{\"Body\":{\"ViewId\":{\"Id\":1,\"Namespace\":1},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"Body\":{\"ViewId\":{\"Id\":2,\"Namespace\":1},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; + char* result = "{\"Body\":[{\"Body\":{\"ViewId\":\"ns=1;i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"Body\":{\"ViewId\":\"ns=1;i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2535,7 +2529,7 @@ START_TEST(UA_ExtensionObject_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":{\"Id\":1},\"Body\":false}"; + char* result = "{\"TypeId\":\"i=1\",\"Body\":false}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2562,7 +2556,7 @@ START_TEST(UA_ExtensionObject_xml_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":{\"Id\":1234,\"Namespace\":2},\"Encoding\":2,\"Body\":\"\"}"; + char* result = "{\"TypeId\":\"ns=2;i=1234\",\"Encoding\":2,\"Body\":\"\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2590,7 +2584,7 @@ START_TEST(UA_ExtensionObject_byteString_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":{\"Id\":1234,\"Namespace\":2},\"Encoding\":1,\"Body\":\"123456789012345678901234567890\"}"; + char* result = "{\"TypeId\":\"ns=2;i=1234\",\"Encoding\":1,\"Body\":\"123456789012345678901234567890\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2644,7 +2638,7 @@ START_TEST(UA_ExpandedNodeId_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"testtestTest\",\"Namespace\":\"asdf\",\"ServerUri\":1345}"; + char* result = "\"svr=1345;nsu=asdf;s=testtestTest\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2652,7 +2646,6 @@ START_TEST(UA_ExpandedNodeId_json_encode) { } END_TEST - START_TEST(UA_ExpandedNodeId_MissingNamespaceUri_json_encode) { UA_ExpandedNodeId *src = UA_ExpandedNodeId_new(); UA_ExpandedNodeId_init(src); @@ -2669,7 +2662,7 @@ START_TEST(UA_ExpandedNodeId_MissingNamespaceUri_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"testtestTest\",\"Namespace\":23,\"ServerUri\":1345}"; + char* result = "\"svr=1345;ns=23;s=testtestTest\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2698,7 +2691,7 @@ START_TEST(UA_ExpandedNodeId_NonReversible_Ns1_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"testtestTest\",\"Namespace\":1,\"ServerUri\":\"uri1\"}"; + char* result = "\"svu=uri1;ns=1;s=testtestTest\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2736,7 +2729,7 @@ START_TEST(UA_ExpandedNodeId_NonReversible_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"testtestTest\",\"Namespace\":\"ns2\",\"ServerUri\":\"uri1\"}"; + char* result = "\"svu=uri1;nsu=ns2;s=testtestTest\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2774,7 +2767,7 @@ START_TEST(UA_ExpandedNodeId_NonReversible_NamespaceUriGiven_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"IdType\":1,\"Id\":\"testtestTest\",\"Namespace\":\"NamespaceUri\",\"ServerUri\":\"uri1\"}"; + char* result = "\"svu=uri1;nsu=NamespaceUri;s=testtestTest\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2939,7 +2932,7 @@ START_TEST(UA_MessageReadResponse_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":0,\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"TypeId\":{\"Id\":1},\"Body\":false}},\"Results\":[{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; + char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":0,\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"Results\":[{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2965,7 +2958,7 @@ START_TEST(UA_ViewDescription_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"ViewId\":{\"Id\":99999},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}"; + char* result = "{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -3073,7 +3066,7 @@ START_TEST(UA_WriteRequest_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"RequestHeader\":{\"AuthenticationToken\":{\"IdType\":1,\"Id\":\"authToken\"},\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"TypeId\":{\"Id\":1},\"Body\":false}},\"NodesToWrite\":[{\"NodeId\":{\"IdType\":1,\"Id\":\"a1111\"},\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":{\"IdType\":1,\"Id\":\"a2222\"},\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; + char* result = "{\"RequestHeader\":{\"AuthenticationToken\":\"s=authToken\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"NodesToWrite\":[{\"NodeId\":\"s=a1111\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":\"s=a2222\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -3118,7 +3111,7 @@ START_TEST(UA_VariableAttributes_json_encode) { "\"Description\":{\"Locale\":\"en-US\",\"Text\":\"the answer\"}," "\"WriteMask\":0,\"UserWriteMask\":0," "\"Value\":{\"Type\":6,\"Body\":42}," - "\"DataType\":{\"Id\":6},\"ValueRank\":-2," + "\"DataType\":\"i=6\",\"ValueRank\":-2," "\"ArrayDimensions\":[]," "\"IsAbstract\":true}"; buf.data[size] = 0; /* zero terminate */ @@ -4404,7 +4397,7 @@ START_TEST(UA_ViewDescription_json_decode) { // given UA_ViewDescription out; UA_ViewDescription_init(&out); - UA_ByteString buf = UA_STRING("{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236,\"ViewId\":{\"Id\":\"00000009-0002-027C-F3BF-BB7BEEFEEFBE\",\"IdType\":2}}"); + UA_ByteString buf = UA_STRING("{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236,\"ViewId\":\"g=00000009-0002-027C-F3BF-BB7BEEFEEFBE\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VIEWDESCRIPTION], NULL); @@ -4433,25 +4426,6 @@ END_TEST /* -----------------NodeId----------------------------- */ START_TEST(UA_NodeId_Nummeric_json_decode) { - // given - UA_NodeId out; - UA_NodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"Id\":42}"); - // when - - UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_NODEID], NULL); - // then - ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(out.identifier.numeric, 42); - ck_assert_uint_eq(out.namespaceIndex, 0); - ck_assert_int_eq(out.identifierType, UA_NODEIDTYPE_NUMERIC); - - UA_NodeId_clear(&out); -} -END_TEST - -#ifdef UA_ENABLE_PARSING -START_TEST(UA_NodeId_Nummeric_json_decode_string) { // given UA_NodeId out; UA_NodeId_init(&out); @@ -4468,13 +4442,12 @@ START_TEST(UA_NodeId_Nummeric_json_decode_string) { UA_NodeId_clear(&out); } END_TEST -#endif START_TEST(UA_NodeId_Nummeric_Namespace_json_decode) { // given UA_NodeId out; UA_NodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"Id\":42,\"Namespace\":123}"); + UA_ByteString buf = UA_STRING("\"ns=123;i=42\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_NODEID], NULL); @@ -4488,12 +4461,11 @@ START_TEST(UA_NodeId_Nummeric_Namespace_json_decode) { } END_TEST - START_TEST(UA_NodeId_String_json_decode) { // given UA_NodeId out; UA_NodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":1,\"Id\":\"test123\"}"); + UA_ByteString buf = UA_STRING("\"s=test123\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_NODEID], NULL); @@ -4519,7 +4491,7 @@ START_TEST(UA_NodeId_Guid_json_decode) { // given UA_NodeId out; UA_NodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":2,\"Id\":\"00000001-0002-0003-0405-060708090A0B\"}"); + UA_ByteString buf = UA_STRING("\"g=00000001-0002-0003-0405-060708090A0B\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_NODEID], NULL); @@ -4548,7 +4520,7 @@ START_TEST(UA_NodeId_ByteString_json_decode) { // given UA_NodeId out; UA_NodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":3,\"Id\":\"YXNkZmFzZGY=\"}"); + UA_ByteString buf = UA_STRING("\"b=YXNkZmFzZGY=\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_NODEID], NULL); @@ -4576,7 +4548,7 @@ START_TEST(UA_ExpandedNodeId_Nummeric_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"Id\":42}"); + UA_ByteString buf = UA_STRING("\"i=42\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4592,33 +4564,11 @@ START_TEST(UA_ExpandedNodeId_Nummeric_json_decode) { } END_TEST -#ifdef UA_ENABLE_PARSING -START_TEST(UA_ExpandedNodeId_Nummeric_json_decode_string) { - // given - UA_ExpandedNodeId out; - UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("\"svr=5;i=42\""); - // when - - UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); - // then - ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - ck_assert_int_eq(out.nodeId.identifier.numeric, 42); - ck_assert_int_eq(out.nodeId.identifierType, UA_NODEIDTYPE_NUMERIC); - ck_assert_ptr_eq(out.namespaceUri.data, NULL); - ck_assert_uint_eq(out.namespaceUri.length, 0); - ck_assert_int_eq(out.serverIndex, 5); - - UA_ExpandedNodeId_clear(&out); -} -END_TEST -#endif - START_TEST(UA_ExpandedNodeId_String_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":1,\"Id\":\"test\"}"); + UA_ByteString buf = UA_STRING("\"s=test\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4639,7 +4589,7 @@ START_TEST(UA_ExpandedNodeId_String_Namespace_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":1,\"Id\":\"test\",\"Namespace\":\"abcdef\"}"); + UA_ByteString buf = UA_STRING("\"nsu=abcdef;s=test\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4668,7 +4618,7 @@ START_TEST(UA_ExpandedNodeId_String_NamespaceAsIndex_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":1,\"Id\":\"test\",\"Namespace\":42}"); + UA_ByteString buf = UA_STRING("\"ns=42;s=test\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4693,7 +4643,7 @@ START_TEST(UA_ExpandedNodeId_String_Namespace_ServerUri_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":1,\"Id\":\"test\",\"Namespace\":\"test\",\"ServerUri\":13}"); + UA_ByteString buf = UA_STRING("\"svr=13;nsu=test;s=test\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4719,7 +4669,7 @@ START_TEST(UA_ExpandedNodeId_ByteString_json_decode) { // given UA_ExpandedNodeId out; UA_ExpandedNodeId_init(&out); - UA_ByteString buf = UA_STRING("{\"IdType\":3,\"Id\":\"YXNkZmFzZGY=\",\"Namespace\":\"test\",\"ServerUri\":13}"); + UA_ByteString buf = UA_STRING("\"svr=13;nsu=test;b=YXNkZmFzZGY=\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXPANDEDNODEID], NULL); @@ -4851,7 +4801,7 @@ START_TEST(UA_ExtensionObject_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"TypeId\":{\"Id\":1},\"Body\":true}"); + UA_ByteString buf = UA_STRING("{\"TypeId\":\"i=1\",\"Body\":true}"); // when @@ -4870,7 +4820,7 @@ START_TEST(UA_ExtensionObject_EncodedByteString_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"Encoding\":1,\"TypeId\":{\"Id\":42},\"Body\":\"YXNkZmFzZGY=\"}"); + UA_ByteString buf = UA_STRING("{\"Encoding\":1,\"TypeId\":\"i=42\",\"Body\":\"YXNkZmFzZGY=\"}"); // when @@ -4898,7 +4848,7 @@ START_TEST(UA_ExtensionObject_EncodedXml_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"Encoding\":2,\"TypeId\":{\"Id\":42},\"Body\":\"\"}"); + UA_ByteString buf = UA_STRING("{\"Encoding\":2,\"TypeId\":\"i=42\",\"Body\":\"\"}"); // when @@ -4920,7 +4870,7 @@ START_TEST(UA_ExtensionObject_Unkown_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"TypeId\":{\"Id\":4711},\"Body\":{\"unknown\":\"body\",\"saveas\":\"Bytestring\"}}"); + UA_ByteString buf = UA_STRING("{\"TypeId\":\"i=4711\",\"Body\":{\"unknown\":\"body\",\"saveas\":\"Bytestring\"}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL); @@ -5186,7 +5136,7 @@ START_TEST(UA_Variant_ExtensionObjectArray_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":{\"Id\":1},\"Body\":false}, {\"TypeId\":{\"Id\":1},\"Body\":true}]}"); + UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":\"i=1\",\"Body\":false}, {\"TypeId\":\"i=1\",\"Body\":true}]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5204,7 +5154,7 @@ START_TEST(UA_Variant_MixedExtensionObjectArray_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":{\"Id\":1},\"Body\":false}, {\"TypeId\":{\"Id\":2},\"Body\":1}]}"); + UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":\"i=1\",\"Body\":false}, {\"TypeId\":\"i=2\",\"Body\":1}]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5238,7 +5188,7 @@ START_TEST(UA_Variant_ExtensionObjectWrap_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":{\"TypeId\":{\"Id\":511},\"Body\":{\"ViewId\":{\"Id\":99999},\"Timestamp\":\"1970-01-15T06:56:07.000Z\",\"ViewVersion\":1236}}}"); + UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07.000Z\",\"ViewVersion\":1236}}}"); // when @@ -5780,23 +5730,13 @@ static Suite *testSuite_builtin_json(void) { //-NodeId- tcase_add_test(tc_json_decode, UA_NodeId_Nummeric_json_decode); -#ifdef UA_ENABLE_PARSING - tcase_add_test(tc_json_decode, UA_NodeId_Nummeric_json_decode_string); -#endif tcase_add_test(tc_json_decode, UA_NodeId_Nummeric_Namespace_json_decode); - tcase_add_test(tc_json_decode, UA_NodeId_String_json_decode); - tcase_add_test(tc_json_decode, UA_NodeId_Guid_json_decode); - tcase_add_test(tc_json_decode, UA_NodeId_ByteString_json_decode); - //expandednodeid tcase_add_test(tc_json_decode, UA_ExpandedNodeId_Nummeric_json_decode); -#ifdef UA_ENABLE_PARSING - tcase_add_test(tc_json_decode, UA_ExpandedNodeId_Nummeric_json_decode_string); -#endif tcase_add_test(tc_json_decode, UA_ExpandedNodeId_String_json_decode); tcase_add_test(tc_json_decode, UA_ExpandedNodeId_String_Namespace_json_decode); tcase_add_test(tc_json_decode, UA_ExpandedNodeId_String_NamespaceAsIndex_json_decode); From 04dc6f8d080375680754f4e78da5858764c1a114 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 26 Dec 2024 19:53:22 +0100 Subject: [PATCH 097/158] feat(core): Printing of QualifiedName according to the v1.05 spec --- include/open62541/types.h | 22 +++++++++++++++ src/ua_types.c | 57 +++++++++++++++++++++++++++++++++++++++ tests/check_utils.c | 33 +++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/include/open62541/types.h b/include/open62541/types.h index 0c8080def..95d456b82 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -709,6 +709,28 @@ UA_INLINABLE(UA_QualifiedName return qn; }) +/* Print the human-readable QualifiedName format. QualifiedNames can be printed + * with either the integer NamespaceIndex or using the NamespaceUri. + * The Namespace 0 is always omitted. + * + * The extended printing tries to translate the NamespaceIndex to the + * NamespaceUri from the mapping table. When the mapping fails, the integer + * NamespaceIndex from is used. + * + * Examples: + * Namespace Zero: HelloWorld + * NamespaceIndex Form: 3:HelloWorld + * NamespaceUri Form: nsu=http://widgets.com/schemas/hello;HelloWorld + * + * The method can either use a pre-allocated string buffer or allocates memory + * internally if called with an empty output string. */ +UA_StatusCode UA_EXPORT +UA_QualifiedName_print(const UA_QualifiedName *qn, UA_String *output); + +UA_StatusCode UA_EXPORT +UA_QualifiedName_printEx(const UA_QualifiedName *qn, UA_String *output, + const UA_NamespaceMapping *nsMapping); + /** * LocalizedText * ^^^^^^^^^^^^^ diff --git a/src/ua_types.c b/src/ua_types.c index 7910bcb6f..e0c958212 100644 --- a/src/ua_types.c +++ b/src/ua_types.c @@ -257,6 +257,63 @@ UA_QualifiedName_hash(const UA_QualifiedName *q) { q->name.data, q->name.length); } +UA_StatusCode +UA_QualifiedName_printEx(const UA_QualifiedName *qn, UA_String *output, + const UA_NamespaceMapping *nsMapping) { + /* Start tracking the output length */ + size_t len = qn->name.length; + + /* Try to map the NamespaceIndex to the Uri */ + UA_String nsUri = UA_STRING_NULL; + if(qn->namespaceIndex > 0 && nsMapping) { + UA_NamespaceMapping_index2Uri(nsMapping, qn->namespaceIndex, &nsUri); + if(nsUri.length > 0) + len += nsUri.length + 1; + } + + /* Print the NamespaceIndex */ + char nsStr[6]; + size_t nsStrSize = 0; + if(nsUri.length == 0 && qn->namespaceIndex > 0) { + nsStrSize = itoaUnsigned(qn->namespaceIndex, nsStr, 10); + len += 1 + nsStrSize; + } + + /* Allocate memory if required */ + if(output->length == 0) { + UA_StatusCode res = UA_ByteString_allocBuffer((UA_ByteString*)output, len); + if(res != UA_STATUSCODE_GOOD) + return res; + } else { + if(output->length < len) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + output->length = len; + } + + /* Print the namespace */ + char *pos = (char*)output->data; + if(nsUri.length > 0) { + memcpy(pos, nsUri.data, nsUri.length); + pos += nsUri.length; + *pos++ = ';'; + } else if(qn->namespaceIndex > 0) { + memcpy(pos, nsStr, nsStrSize); + pos += nsStrSize; + *pos++ = ':'; + } + + /* Print the name */ + memcpy(pos, qn->name.data, qn->name.length); + + UA_assert(output->length == (size_t)((UA_Byte*)pos + qn->name.length - output->data)); + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_QualifiedName_print(const UA_QualifiedName *qn, UA_String *output) { + return UA_QualifiedName_printEx(qn, output, NULL); +} + /* DateTime */ UA_DateTimeStruct UA_DateTime_toStruct(UA_DateTime t) { diff --git a/tests/check_utils.c b/tests/check_utils.c index 30b81f2bf..c2b1141de 100644 --- a/tests/check_utils.c +++ b/tests/check_utils.c @@ -647,6 +647,34 @@ START_TEST(expIdToStringNumericWithMapping) { UA_String_clear(&str); } END_TEST +START_TEST(qualifiedNameNsUri) { + UA_String namespaces[2] = { + UA_STRING_STATIC("ns1"), + UA_STRING_STATIC("ns2") + }; + + UA_NamespaceMapping nsMapping; + memset(&nsMapping, 0, sizeof(UA_NamespaceMapping)); + nsMapping.namespaceUris = namespaces; + nsMapping.namespaceUrisSize = 2; + + UA_QualifiedName qn = UA_QUALIFIEDNAME(1, "name"); + UA_String str = UA_STRING_NULL; + + UA_QualifiedName_printEx(&qn, &str, &nsMapping); + assertNodeIdString(&str, "ns2;name"); + UA_String_clear(&str); +} END_TEST + +START_TEST(qualifiedNameNsIndex) { + UA_QualifiedName qn = UA_QUALIFIEDNAME(1, "name"); + UA_String str = UA_STRING_NULL; + + UA_QualifiedName_printEx(&qn, &str, NULL); + assertNodeIdString(&str, "1:name"); + UA_String_clear(&str); +} END_TEST + static Suite* testSuite_Utils(void) { Suite *s = suite_create("Utils"); TCase *tc_endpointUrl_split = tcase_create("EndpointUrl_split"); @@ -683,6 +711,11 @@ static Suite* testSuite_Utils(void) { tcase_add_test(tc3, expIdToStringNumericWithMapping); suite_add_tcase(s, tc3); + TCase *tc4 = tcase_create("test qualifiedname string"); + tcase_add_test(tc4, qualifiedNameNsUri); + tcase_add_test(tc4, qualifiedNameNsIndex); + suite_add_tcase(s, tc4); + return s; } From 59da360dee2234c31ab02f02f965f45070e5e777 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 26 Dec 2024 20:18:50 +0100 Subject: [PATCH 098/158] feat(core): Parsing of human-readable QualifiedNames with the Uri component --- include/open62541/types.h | 14 +++ src/util/ua_types_lex.c | 251 ++++++++++++++++++++++++++++---------- src/util/ua_types_lex.re | 69 ++++++++--- tests/check_utils.c | 12 ++ 4 files changed, 267 insertions(+), 79 deletions(-) diff --git a/include/open62541/types.h b/include/open62541/types.h index 95d456b82..0019c18fc 100644 --- a/include/open62541/types.h +++ b/include/open62541/types.h @@ -731,6 +731,20 @@ UA_StatusCode UA_EXPORT UA_QualifiedName_printEx(const UA_QualifiedName *qn, UA_String *output, const UA_NamespaceMapping *nsMapping); +#ifdef UA_ENABLE_PARSING +/* Parse the human-readable QualifiedName format. + * + * The extended parsing tries to translate the NamespaceIndex to a NamespaceUri + * from the mapping table. When the mapping fails, the name component gets the + * entire string. */ +UA_StatusCode UA_EXPORT +UA_QualifiedName_parse(UA_QualifiedName *qn, const UA_String str); + +UA_StatusCode UA_EXPORT +UA_QualifiedName_parseEx(UA_QualifiedName *qn, const UA_String str, + const UA_NamespaceMapping *nsMapping); +#endif + /** * LocalizedText * ^^^^^^^^^^^^^ diff --git a/src/util/ua_types_lex.c b/src/util/ua_types_lex.c index ded210f29..b6b0206d7 100644 --- a/src/util/ua_types_lex.c +++ b/src/util/ua_types_lex.c @@ -695,7 +695,7 @@ relativepath_addelem(UA_RelativePath *rp, UA_RelativePathElement *el) { /* Parse name string with '&' as the escape character */ static UA_StatusCode -parse_refpath_qn_name(UA_String *name, const char *pos, +parse_qn_name(UA_String *name, const char *pos, const char *end, Escaping esc) { /* There must be no unescaped characters (also trailing &) */ char *end_esc = find_unescaped((char *)(uintptr_t)pos, end, @@ -721,24 +721,151 @@ parse_refpath_qn_name(UA_String *name, const char *pos, } static UA_StatusCode -parse_refpath_qn(UA_QualifiedName *qn, const char *pos, const char *end, Escaping esc) { +parse_qn(UA_QualifiedName *qn, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping) { + size_t len; + UA_UInt32 tmp; + UA_String nsUri; + const char *begin = pos; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + UA_QualifiedName_init(qn); - /* Parse the NamespaceIndex if we find a colon */ - for(const char *col = pos; col < end; col++) { - if(*col == ':') { - UA_UInt32 tmp; - size_t len = (size_t)(col - pos); - if(UA_readNumber((const UA_Byte*)pos, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - qn->namespaceIndex = (UA_UInt16)tmp; - pos = col + 1; - break; - } + LexContext context; + memset(&context, 0, sizeof(LexContext)); + + +{ + char yych; + unsigned int yyaccept = 0; + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy41; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy44; + case ';': goto yy45; + default: goto yy43; + } +yy41: + YYSKIP(); +yy42: + { pos = begin; goto match_name; } +yy43: + yyaccept = 0; + YYSKIP(); + YYBACKUP(); + yych = YYPEEK(); + if (yych <= 0x00) goto yy42; + goto yy47; +yy44: + yyaccept = 0; + YYSKIP(); + YYBACKUP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy42; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy49; + case ':': goto yy50; + default: goto yy47; + } +yy45: + YYSKIP(); + { goto match_uri; } +yy46: + YYSKIP(); + yych = YYPEEK(); +yy47: + switch (yych) { + case 0x00: goto yy48; + case ';': goto yy45; + default: goto yy46; + } +yy48: + YYRESTORE(); + if (yyaccept == 0) goto yy42; + else goto yy51; +yy49: + YYSKIP(); + yych = YYPEEK(); + switch (yych) { + case 0x00: goto yy48; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy49; + case ':': goto yy50; + case ';': goto yy45; + default: goto yy46; + } +yy50: + yyaccept = 1; + YYSKIP(); + YYBACKUP(); + yych = YYPEEK(); + if (yych >= 0x01) goto yy47; +yy51: + { goto match_index; } +} + + + match_index: + len = (size_t)(pos - 1 - begin); + if(UA_readNumber((const UA_Byte*)begin, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + qn->namespaceIndex = (UA_UInt16)tmp; + goto match_name; + + match_uri: + nsUri.length = (size_t)(pos - 1 - begin); + nsUri.data = (UA_Byte*)(uintptr_t)begin; + if(nsMapping) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, &qn->namespaceIndex); + if(res != UA_STATUSCODE_GOOD) { + UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; + return UA_String_copy(&total, &qn->name); } - /* Parse the (escaped) name */ - return parse_refpath_qn_name(&qn->name, pos, end, esc); + match_name: + return parse_qn_name(&qn->name, pos, end, esc); +} + +UA_StatusCode +UA_QualifiedName_parseEx(UA_QualifiedName *qn, const UA_String str, + const UA_NamespaceMapping *nsMapping) { + const char *pos = (const char*)str.data; + const char *end = (const char*)str.data + str.length; + UA_StatusCode res = parse_qn(qn, pos, end, ESCAPING_NONE, nsMapping); + if(res != UA_STATUSCODE_GOOD) + UA_QualifiedName_clear(qn); + return res; +} + +UA_StatusCode +UA_QualifiedName_parse(UA_QualifiedName *qn, const UA_String str) { + return UA_QualifiedName_parseEx(qn, str, NULL); } static UA_StatusCode @@ -765,67 +892,67 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, unsigned int yyaccept = 0; yych = YYPEEK(); switch (yych) { - case '.': goto yy43; - case '/': goto yy44; - case '<': goto yy45; - default: goto yy41; + case '.': goto yy55; + case '/': goto yy56; + case '<': goto yy57; + default: goto yy53; } -yy41: +yy53: YYSKIP(); -yy42: +yy54: { *ppos = pos-1; return UA_STATUSCODE_GOOD; } -yy43: +yy55: YYSKIP(); { current.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_AGGREGATES); goto reftype_target; } -yy44: +yy56: YYSKIP(); { current.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES); goto reftype_target; } -yy45: +yy57: yyaccept = 0; YYSKIP(); YYBACKUP(); yych = YYPEEK(); switch (yych) { case 0x00: - case '>': goto yy42; + case '>': goto yy54; case '&': YYSTAGP(context.yyt1); - goto yy48; + goto yy60; default: YYSTAGP(context.yyt1); - goto yy46; + goto yy58; } -yy46: +yy58: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy47; - case '&': goto yy48; - case '>': goto yy49; - default: goto yy46; + case 0x00: goto yy59; + case '&': goto yy60; + case '>': goto yy61; + default: goto yy58; } -yy47: +yy59: YYRESTORE(); - if (yyaccept == 0) goto yy42; - else goto yy50; -yy48: + if (yyaccept == 0) goto yy54; + else goto yy62; +yy60: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy47; - case '&': goto yy48; - case '>': goto yy51; - default: goto yy46; + case 0x00: goto yy59; + case '&': goto yy60; + case '>': goto yy63; + default: goto yy58; } -yy49: +yy61: YYSKIP(); -yy50: +yy62: begin = context.yyt1; YYSTAGP(finish); YYSHIFTSTAG(finish, -1); @@ -848,21 +975,21 @@ yy50: // Parse the the ReferenceType from its BrowseName UA_QualifiedName refqn; - res = parse_refpath_qn(&refqn, begin, finish, esc); + res = parse_qn(&refqn, begin, finish, esc, NULL); res |= lookupRefType(server, &refqn, ¤t.referenceTypeId); UA_QualifiedName_clear(&refqn); goto reftype_target; } -yy51: +yy63: yyaccept = 1; YYSKIP(); YYBACKUP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy50; - case '&': goto yy48; - case '>': goto yy49; - default: goto yy46; + case 0x00: goto yy62; + case '&': goto yy60; + case '>': goto yy61; + default: goto yy58; } } @@ -882,18 +1009,18 @@ yy51: case '.': case '/': case '<': - case '[': goto yy53; + case '[': goto yy65; case '&': YYSTAGP(context.yyt1); - goto yy56; + goto yy68; default: YYSTAGP(context.yyt1); - goto yy54; + goto yy66; } -yy53: +yy65: YYSKIP(); { pos--; goto add_element; } -yy54: +yy66: YYSKIP(); yych = YYPEEK(); switch (yych) { @@ -902,23 +1029,23 @@ yy54: case '.': case '/': case '<': - case '[': goto yy55; - case '&': goto yy56; - default: goto yy54; + case '[': goto yy67; + case '&': goto yy68; + default: goto yy66; } -yy55: +yy67: begin = context.yyt1; { - res = parse_refpath_qn(¤t.targetName, begin, pos, esc); + res = parse_qn(¤t.targetName, begin, pos, esc, NULL); goto add_element; } -yy56: +yy68: YYSKIP(); yych = YYPEEK(); switch (yych) { - case 0x00: goto yy55; - case '&': goto yy56; - default: goto yy54; + case 0x00: goto yy67; + case '&': goto yy68; + default: goto yy66; } } diff --git a/src/util/ua_types_lex.re b/src/util/ua_types_lex.re index 56a1b75c3..d28dc66f3 100644 --- a/src/util/ua_types_lex.re +++ b/src/util/ua_types_lex.re @@ -305,7 +305,7 @@ relativepath_addelem(UA_RelativePath *rp, UA_RelativePathElement *el) { /* Parse name string with '&' as the escape character */ static UA_StatusCode -parse_refpath_qn_name(UA_String *name, const char *pos, +parse_qn_name(UA_String *name, const char *pos, const char *end, Escaping esc) { /* There must be no unescaped characters (also trailing &) */ char *end_esc = find_unescaped((char *)(uintptr_t)pos, end, @@ -331,24 +331,59 @@ parse_refpath_qn_name(UA_String *name, const char *pos, } static UA_StatusCode -parse_refpath_qn(UA_QualifiedName *qn, const char *pos, const char *end, Escaping esc) { +parse_qn(UA_QualifiedName *qn, const char *pos, const char *end, + Escaping esc, const UA_NamespaceMapping *nsMapping) { + size_t len; + UA_UInt32 tmp; + UA_String nsUri; + const char *begin = pos; + UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; + UA_QualifiedName_init(qn); - /* Parse the NamespaceIndex if we find a colon */ - for(const char *col = pos; col < end; col++) { - if(*col == ':') { - UA_UInt32 tmp; - size_t len = (size_t)(col - pos); - if(UA_readNumber((const UA_Byte*)pos, len, &tmp) != len) - return UA_STATUSCODE_BADDECODINGERROR; - qn->namespaceIndex = (UA_UInt16)tmp; - pos = col + 1; - break; - } + LexContext context; + memset(&context, 0, sizeof(LexContext)); + + /*!re2c // Match the grammar + [0-9]+ ":" { goto match_index; } + escaped_uri ";" { goto match_uri; } + * { pos = begin; goto match_name; } */ + + match_index: + len = (size_t)(pos - 1 - begin); + if(UA_readNumber((const UA_Byte*)begin, len, &tmp) != len) + return UA_STATUSCODE_BADDECODINGERROR; + qn->namespaceIndex = (UA_UInt16)tmp; + goto match_name; + + match_uri: + nsUri.length = (size_t)(pos - 1 - begin); + nsUri.data = (UA_Byte*)(uintptr_t)begin; + if(nsMapping) + res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, &qn->namespaceIndex); + if(res != UA_STATUSCODE_GOOD) { + UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; + return UA_String_copy(&total, &qn->name); } - /* Parse the (escaped) name */ - return parse_refpath_qn_name(&qn->name, pos, end, esc); + match_name: + return parse_qn_name(&qn->name, pos, end, esc); +} + +UA_StatusCode +UA_QualifiedName_parseEx(UA_QualifiedName *qn, const UA_String str, + const UA_NamespaceMapping *nsMapping) { + const char *pos = (const char*)str.data; + const char *end = (const char*)str.data + str.length; + UA_StatusCode res = parse_qn(qn, pos, end, ESCAPING_NONE, nsMapping); + if(res != UA_STATUSCODE_GOOD) + UA_QualifiedName_clear(qn); + return res; +} + +UA_StatusCode +UA_QualifiedName_parse(UA_QualifiedName *qn, const UA_String str) { + return UA_QualifiedName_parseEx(qn, str, NULL); } static UA_StatusCode @@ -400,7 +435,7 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, // Parse the the ReferenceType from its BrowseName UA_QualifiedName refqn; - res = parse_refpath_qn(&refqn, begin, finish, esc); + res = parse_qn(&refqn, begin, finish, esc, NULL); res |= lookupRefType(server, &refqn, ¤t.referenceTypeId); UA_QualifiedName_clear(&refqn); goto reftype_target; @@ -417,7 +452,7 @@ parse_relativepath(UA_RelativePath *rp, const char **ppos, const char *end, /*!re2c @begin ([^ Date: Thu, 26 Dec 2024 20:30:16 +0100 Subject: [PATCH 099/158] refactor(core): Use the human-readable QualifiedName encoding for JSON --- src/ua_types_encoding_json.c | 54 ++++++--------------------- tests/check_types_builtin_json.c | 63 +++++++++----------------------- 2 files changed, 28 insertions(+), 89 deletions(-) diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index d593fe64f..babb9bddf 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -181,10 +181,6 @@ writeJsonObjElm(CtxJson *ctx, const char *key, static const char* UA_JSONKEY_LOCALE = "Locale"; static const char* UA_JSONKEY_TEXT = "Text"; -/* QualifiedName */ -static const char* UA_JSONKEY_NAME = "Name"; -static const char* UA_JSONKEY_URI = "Uri"; - /* Variant */ static const char* UA_JSONKEY_TYPE = "Type"; static const char* UA_JSONKEY_BODY = "Body"; @@ -654,38 +650,11 @@ ENCODE_JSON(LocalizedText) { } ENCODE_JSON(QualifiedName) { - status ret = writeJsonObjStart(ctx); - ret |= writeJsonKey(ctx, UA_JSONKEY_NAME); - ret |= ENCODE_DIRECT_JSON(&src->name, String); - - if(ctx->useReversible) { - if(src->namespaceIndex != 0) { - ret |= writeJsonKey(ctx, UA_JSONKEY_URI); - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); - } - } else { - /* For the non-reversible form, the NamespaceUri associated with the - * NamespaceIndex portion of the QualifiedName is encoded as JSON string - * unless the NamespaceIndex is 1 or if NamespaceUri is unknown. In - * these cases, the NamespaceIndex is encoded as a JSON number. */ - ret |= writeJsonKey(ctx, UA_JSONKEY_URI); - if(src->namespaceIndex == 1) { - ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); - } else { - /* Check if Namespace given and in range */ - UA_String nsUri = UA_STRING_NULL; - UA_UInt16 ns = src->namespaceIndex; - if(ctx->namespaceMapping) - UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); - if(nsUri.length > 0) { - ret |= ENCODE_DIRECT_JSON(&nsUri, String); - } else { - ret |= ENCODE_DIRECT_JSON(&ns, UInt16); /* If not encode as number */ - } - } - } - - return ret | writeJsonObjEnd(ctx); + UA_String out = UA_STRING_NULL; + UA_StatusCode ret = UA_QualifiedName_printEx(src, &out, ctx->namespaceMapping); + ret |= ENCODE_DIRECT_JSON(&out, String); + UA_String_clear(&out); + return ret; } ENCODE_JSON(StatusCode) { @@ -1548,14 +1517,13 @@ DECODE_JSON(LocalizedText) { } DECODE_JSON(QualifiedName) { - CHECK_OBJECT; + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; - DecodeEntry entries[2] = { - {UA_JSONKEY_NAME, &dst->name, NULL, false, &UA_TYPES[UA_TYPES_STRING]}, - {UA_JSONKEY_URI, &dst->namespaceIndex, NULL, false, &UA_TYPES[UA_TYPES_UINT16]} - }; - - return decodeFields(ctx, entries, 2); + ctx->index++; + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + return UA_QualifiedName_parseEx(dst, str, ctx->namespaceMapping); } UA_FUNC_ATTR_WARN_UNUSED_RESULT status diff --git a/tests/check_types_builtin_json.c b/tests/check_types_builtin_json.c index da1d9f72c..4946018d9 100644 --- a/tests/check_types_builtin_json.c +++ b/tests/check_types_builtin_json.c @@ -1142,7 +1142,7 @@ START_TEST(UA_StatusCode_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "2161770496"; + char* result = "{\"Code\":2161770496,\"Symbol\":\"BadAggregateConfigurationRejected\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1190,7 +1190,7 @@ START_TEST(UA_StatusCode_nonReversible_good_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Code\":0,\"Symbol\":\"Good\"}"; + char* result = "{}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1458,7 +1458,7 @@ START_TEST(UA_DiagInfo_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"SymbolicId\":13,\"NamespaceUri\":15,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"; + char* result = "{\"SymbolicId\":13,\"NamespaceUri\":15,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":{\"Code\":2155216896,\"Symbol\":\"BadArgumentsMissing\"}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1503,14 +1503,11 @@ START_TEST(UA_DiagInfo_withInner_json_encode) { UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); - //{"SymbolicId":13,"LocalizedText":14,"Locale":12,"AdditionalInfo":"additionalInfo","InnerStatusCode":2155216896,"InnerDiagnosticInfo":{"AdditionalInfo":"INNER ADDITION INFO"}} - ck_assert_uint_eq(size, 174); - status s = UA_encodeJson((void *) src, type, &buf, NULL); ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896,\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO\"}}"; + char* result = "{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":{\"Code\":2155216896,\"Symbol\":\"BadArgumentsMissing\"},\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO\"}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1572,7 +1569,7 @@ START_TEST(UA_DiagInfo_withTwoInner_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896,\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO\",\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO2\"}}}"; + char* result = "{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":{\"Code\":2155216896,\"Symbol\":\"BadArgumentsMissing\"},\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO\",\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO2\"}}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1724,32 +1721,7 @@ START_TEST(UA_QualName_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Name\":\"derName\",\"Uri\":1}"; - buf.data[size] = 0; /* zero terminate */ - ck_assert_str_eq(result, (char*)buf.data); - UA_ByteString_clear(&buf); - UA_QualifiedName_delete(src); -} -END_TEST - - - -START_TEST(UA_QualName_NonReversible_json_encode) { - UA_QualifiedName *src = UA_QualifiedName_new(); - UA_QualifiedName_init(src); - src->name = UA_STRING_ALLOC("derName"); - src->namespaceIndex = 1; - const UA_DataType *type = &UA_TYPES[UA_TYPES_QUALIFIEDNAME]; - size_t size = UA_calcSizeJson((void *) src, type, NULL); - - UA_ByteString buf; - UA_ByteString_allocBuffer(&buf, size+1); - - status s = UA_encodeJson((void *) src, type, &buf, NULL); - ck_assert_int_eq(s, UA_STATUSCODE_GOOD); - - // then - char* result = "{\"Name\":\"derName\",\"Uri\":1}"; + char* result = "\"1:derName\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1782,7 +1754,7 @@ START_TEST(UA_QualName_NonReversible_Namespace_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Name\":\"derName\",\"Uri\":\"ns2\"}"; + char* result = "\"ns2;derName\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1805,7 +1777,7 @@ START_TEST(UA_QualName_NonReversible_NoNamespaceAsNumber_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Name\":\"derName\",\"Uri\":6789}"; + char* result = "\"6789:derName\""; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2122,7 +2094,7 @@ START_TEST(UA_Variant_QualName_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":20,\"Body\":{\"Name\":\"derName\",\"Uri\":1}}"; + char* result = "{\"Type\":20,\"Body\":\"1:derName\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2811,7 +2783,7 @@ START_TEST(UA_DataValue_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07.8901234Z\",\"SourcePicoseconds\":5678,\"ServerTimestamp\":\"1970-01-28T03:34:38.9012345Z\",\"ServerPicoseconds\":6789}"; + char* result = "{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07.8901234Z\",\"SourcePicoseconds\":5678,\"ServerTimestamp\":\"1970-01-28T03:34:38.9012345Z\",\"ServerPicoseconds\":6789}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2932,7 +2904,7 @@ START_TEST(UA_MessageReadResponse_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":0,\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"Results\":[{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; + char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":{},\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"Results\":[{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -3066,7 +3038,7 @@ START_TEST(UA_WriteRequest_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"RequestHeader\":{\"AuthenticationToken\":\"s=authToken\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"NodesToWrite\":[{\"NodeId\":\"s=a1111\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":\"s=a2222\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; + char* result = "{\"RequestHeader\":{\"AuthenticationToken\":\"s=authToken\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"NodesToWrite\":[{\"NodeId\":\"s=a1111\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":\"s=a2222\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -4104,7 +4076,7 @@ END_TEST START_TEST(UA_StatusCode_2_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":2}"); + UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":{\"Code\":2}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4135,7 +4107,7 @@ END_TEST START_TEST(UA_StatusCode_0_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":{}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4316,7 +4288,7 @@ START_TEST(UA_QualifiedName_json_decode) { // given UA_QualifiedName out; UA_QualifiedName_init(&out); - UA_ByteString buf = UA_STRING("{\"Name\":\"derName\",\"Uri\":1}"); + UA_ByteString buf = UA_STRING("\"1:derName\""); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_QUALIFIEDNAME], NULL); @@ -4705,7 +4677,7 @@ START_TEST(UA_DiagnosticInfo_json_decode) { "\"LocalizedText\":14," "\"Locale\":12," "\"AdditionalInfo\":\"additionalInfo\"," - "\"InnerStatusCode\":2155216896," + "\"InnerStatusCode\":{\"Code\":2155216896}," "\"InnerDiagnosticInfo\":{\"AdditionalInfo\":\"INNER ADDITION INFO\"}}"); // when @@ -4739,7 +4711,7 @@ START_TEST(UA_DataValue_json_decode) { UA_DataValue out; UA_DataValue_init(&out); - UA_ByteString buf = UA_STRING("{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":2153250816,\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"SourcePicoseconds\":0,\"ServerTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerPicoseconds\":0}"); + UA_ByteString buf = UA_STRING("{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"SourcePicoseconds\":0,\"ServerTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerPicoseconds\":0}"); // when @@ -5561,7 +5533,6 @@ static Suite *testSuite_builtin_json(void) { //QualifiedName tcase_add_test(tc_json_encode, UA_QualName_json_encode); - tcase_add_test(tc_json_encode, UA_QualName_NonReversible_json_encode); tcase_add_test(tc_json_encode, UA_QualName_NonReversible_Namespace_json_encode); tcase_add_test(tc_json_encode, UA_QualName_NonReversible_NoNamespaceAsNumber_json_encode); From 458979903aaa363a828418ba715d87857ae478b3 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 26 Dec 2024 22:07:18 +0100 Subject: [PATCH 100/158] refactor(core): Use StatusCode decoding from the 1.05 spec --- src/ua_types_encoding_json.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index babb9bddf..26586ed14 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -658,18 +658,19 @@ ENCODE_JSON(QualifiedName) { } ENCODE_JSON(StatusCode) { - if(ctx->useReversible) - return ENCODE_DIRECT_JSON(src, UInt32); - const char *codename = UA_StatusCode_name(*src); UA_String statusDescription = UA_STRING((char*)(uintptr_t)codename); status ret = UA_STATUSCODE_GOOD; ret |= writeJsonObjStart(ctx); - ret |= writeJsonKey(ctx, UA_JSONKEY_CODE); - ret |= ENCODE_DIRECT_JSON(src, UInt32); - ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOL); - ret |= ENCODE_DIRECT_JSON(&statusDescription, String); + if(*src > UA_STATUSCODE_GOOD) { + ret |= writeJsonKey(ctx, UA_JSONKEY_CODE); + ret |= ENCODE_DIRECT_JSON(src, UInt32); + if(codename) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOL); + ret |= ENCODE_DIRECT_JSON(&statusDescription, String); + } + } ret |= writeJsonObjEnd(ctx); return ret; } @@ -1714,8 +1715,20 @@ DECODE_JSON(DateTime) { return UA_STATUSCODE_GOOD; } +static UA_StatusCode +decodeJsonNop(ParseCtx *ctx, void *dst, const UA_DataType *type) { + return UA_STATUSCODE_GOOD; +} + DECODE_JSON(StatusCode) { - return UInt32_decodeJson(ctx, dst, NULL); + CHECK_OBJECT; + + DecodeEntry entries[2] = { + {UA_JSONKEY_CODE, dst, NULL, false, &UA_TYPES[UA_TYPES_UINT32]}, + {UA_JSONKEY_SYMBOL, NULL, decodeJsonNop, false, &UA_TYPES[UA_TYPES_STRING]} + }; + + return decodeFields(ctx, entries, 2); } /* Get type type encoded by the ExtensionObject at ctx->index. From 4478f1d5562d23aac3527940c6a39525248fcd2a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 27 Dec 2024 17:37:03 +0100 Subject: [PATCH 101/158] refactor(core): JSON encoding of Variant, DataValue, ExtensionObject following 1.05 spec --- CHANGES.md | 5 + src/ua_types_encoding_json.c | 644 +++++++++++++++++-------------- tests/check_types_builtin_json.c | 281 ++++++-------- 3 files changed, 480 insertions(+), 450 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 64818addb..261e1ce58 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,11 @@ refactorings and bug fixes are not reported here. # Development +### JSON encoding changed with the v1.05 specification + +The JSON encoding was reworked for the v1.05 version of the OPC UA +specification. The change breaks backwards compatibility. + ### PubSub NetworkMessage structure has an explicit DataSetMessageSize In prior versions of the standard, when the PayloadHeader was missing, the diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c index 26586ed14..00c2203ea 100644 --- a/src/ua_types_encoding_json.c +++ b/src/ua_types_encoding_json.c @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * - * Copyright 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2014-2018, 2024 (c) Fraunhofer IOSB (Author: Julius Pfrommer) * Copyright 2018 (c) Fraunhofer IOSB (Author: Lukas Meling) */ @@ -51,6 +51,12 @@ /* Encoding */ /************/ +static status +encodeJsonStructureContent(CtxJson *ctx, const void *src, const UA_DataType *type); + +static status +decodeJsonStructure(ParseCtx *ctx, void *dst, const UA_DataType *type); + #define ENCODE_JSON(TYPE) static status \ TYPE##_encodeJson(CtxJson *ctx, const UA_##TYPE *src, const UA_DataType *type) @@ -182,12 +188,11 @@ static const char* UA_JSONKEY_LOCALE = "Locale"; static const char* UA_JSONKEY_TEXT = "Text"; /* Variant */ -static const char* UA_JSONKEY_TYPE = "Type"; -static const char* UA_JSONKEY_BODY = "Body"; -static const char* UA_JSONKEY_DIMENSION = "Dimension"; +static const char* UA_JSONKEY_TYPE = "UaType"; +static const char* UA_JSONKEY_VALUE = "Value"; +static const char* UA_JSONKEY_DIMENSIONS = "Dimensions"; /* DataValue */ -static const char* UA_JSONKEY_VALUE = "Value"; static const char* UA_JSONKEY_STATUS = "Status"; static const char* UA_JSONKEY_SOURCETIMESTAMP = "SourceTimestamp"; static const char* UA_JSONKEY_SOURCEPICOSECONDS = "SourcePicoseconds"; @@ -195,8 +200,9 @@ static const char* UA_JSONKEY_SERVERTIMESTAMP = "ServerTimestamp"; static const char* UA_JSONKEY_SERVERPICOSECONDS = "ServerPicoseconds"; /* ExtensionObject */ -static const char* UA_JSONKEY_ENCODING = "Encoding"; -static const char* UA_JSONKEY_TYPEID = "TypeId"; +static const char* UA_JSONKEY_ENCODING = "UaEncoding"; +static const char* UA_JSONKEY_TYPEID = "UaTypeId"; +static const char* UA_JSONKEY_BODY = "UaBody"; /* StatusCode */ static const char* UA_JSONKEY_CODE = "Code"; @@ -635,18 +641,12 @@ ENCODE_JSON(ExpandedNodeId) { /* LocalizedText */ ENCODE_JSON(LocalizedText) { - if(ctx->useReversible) { - status ret = writeJsonObjStart(ctx); - ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); - ret |= ENCODE_DIRECT_JSON(&src->locale, String); - ret |= writeJsonKey(ctx, UA_JSONKEY_TEXT); - ret |= ENCODE_DIRECT_JSON(&src->text, String); - return ret | writeJsonObjEnd(ctx); - } - - /* For the non-reversible form, LocalizedText value shall be encoded as a - * JSON string containing the Text component.*/ - return ENCODE_DIRECT_JSON(&src->text, String); + status ret = writeJsonObjStart(ctx); + ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); + ret |= ENCODE_DIRECT_JSON(&src->locale, String); + ret |= writeJsonKey(ctx, UA_JSONKEY_TEXT); + ret |= ENCODE_DIRECT_JSON(&src->text, String); + return ret | writeJsonObjEnd(ctx); } ENCODE_JSON(QualifiedName) { @@ -688,35 +688,39 @@ ENCODE_JSON(ExtensionObject) { status ret = writeJsonObjStart(ctx); - /* Reversible encoding */ - if(ctx->useReversible) { - /* Write the type NodeId */ - ret |= writeJsonKey(ctx, UA_JSONKEY_TYPEID); - if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || - src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) - ret |= ENCODE_DIRECT_JSON(&src->content.encoded.typeId, NodeId); - else - ret |= ENCODE_DIRECT_JSON(&src->content.decoded.type->typeId, NodeId); + /* Write the type NodeId */ + ret |= writeJsonKey(ctx, UA_JSONKEY_TYPEID); + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || + src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) + ret |= ENCODE_DIRECT_JSON(&src->content.encoded.typeId, NodeId); + else + ret |= ENCODE_DIRECT_JSON(&src->content.decoded.type->typeId, NodeId); - /* Write the encoding */ + /* Write the encoding type and body if encoded */ + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || + src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) { if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) { ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); ret |= writeChar(ctx, '1'); - } else if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) { + } else { ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); ret |= writeChar(ctx, '2'); } + ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); + ret |= ENCODE_DIRECT_JSON(&src->content.encoded.body, String); + return ret | writeJsonObjEnd(ctx); } - /* Write the body */ - ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); - if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || - src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) { - ret |= ENCODE_DIRECT_JSON(&src->content.encoded.body, String); + const UA_DataType *t = src->content.decoded.type; + if(t->typeKind == UA_DATATYPEKIND_STRUCTURE) { + /* Write structures in-situ. + * TODO: Structures with optional fields and unions */ + ret |= encodeJsonStructureContent(ctx, src->content.decoded.data, t); } else { - const UA_DataType *t = src->content.decoded.type; - ret |= encodeJsonJumpTable[t->typeKind] - (ctx, src->content.decoded.data, t); + /* NON-STANDARD: The standard 1.05 doesn't let us print non-structure + * types in ExtensionObjects (e.g. enums). Print them in the body. */ + ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); + ret |= encodeJsonJumpTable[t->typeKind](ctx, src->content.decoded.data, t); } return ret | writeJsonObjEnd(ctx); @@ -777,92 +781,54 @@ encodeArrayJsonWrapExtensionObject(CtxJson *ctx, const void *data, return ret | writeJsonArrEnd(ctx, type); } -static status -addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type, - size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex, - size_t dimensionSize) { - /* Stop recursion: The inner arrays are written */ - status ret; - if(dimensionIndex == (dimensionSize - 1)) { - u8 *ptr = ((u8 *)array) + (type->memSize * *index); - u32 size = arrayDimensions[dimensionIndex]; - (*index) += arrayDimensions[dimensionIndex]; - return encodeArrayJsonWrapExtensionObject(ctx, ptr, size, type); - } - - /* Recurse to the next dimension */ - ret = writeJsonArrStart(ctx); - for(size_t i = 0; i < arrayDimensions[dimensionIndex]; i++) { - ret |= writeJsonBeforeElement(ctx, true); - ret |= addMultiArrayContentJSON(ctx, array, type, index, arrayDimensions, - dimensionIndex + 1, dimensionSize); - ctx->commaNeeded[ctx->depth] = true; - } - return ret | writeJsonArrEnd(ctx, type); -} - -ENCODE_JSON(Variant) { +static UA_StatusCode +encodeVariantInner(CtxJson *ctx, const UA_Variant *src) { /* If type is 0 (NULL) the Variant contains a NULL value and the containing * JSON object shall be omitted or replaced by the JSON literal ‘null’ (when * an element of a JSON array). */ if(!src->type) return writeJsonObjStart(ctx) | writeJsonObjEnd(ctx); - /* Set the content type in the encoding mask */ - const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); - /* Set the array type in the encoding mask */ const bool isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL; - const bool hasDimensions = isArray && src->arrayDimensionsSize > 0; + const bool hasDimensions = isArray && src->arrayDimensionsSize > 1; - /* We cannot directly encode a variant inside a variant (but arrays of - * variant are possible) */ - UA_Boolean wrapEO = !isBuiltin; + /* Wrap the value in an ExtensionObject if not builtin. We cannot directly + * encode a variant inside a variant (but arrays of variant are possible) */ + UA_Boolean wrapEO = (src->type->typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO); if(src->type == &UA_TYPES[UA_TYPES_VARIANT] && !isArray) wrapEO = true; - if(ctx->prettyPrint) - wrapEO = false; /* Don't wrap values in ExtensionObjects for pretty-printing */ - status ret = writeJsonObjStart(ctx); + status ret = UA_STATUSCODE_GOOD; - /* Write the type NodeId */ - if(ctx->useReversible) { - ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); - if(ctx->prettyPrint) { - ret |= writeChars(ctx, src->type->typeName, strlen(src->type->typeName)); - } else { - /* Write the NodeId for the reversible form */ - UA_UInt32 typeId = src->type->typeId.identifier.numeric; - if(wrapEO) - typeId = UA_TYPES[UA_TYPES_EXTENSIONOBJECT].typeId.identifier.numeric; - ret |= ENCODE_DIRECT_JSON(&typeId, UInt32); - } - } - - /* Write the Variant body */ - ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); + /* Write the type number */ + UA_UInt32 typeId = src->type->typeKind + 1; + if(wrapEO) + typeId = UA_TYPES[UA_TYPES_EXTENSIONOBJECT].typeKind + 1; + ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); + ret |= ENCODE_DIRECT_JSON(&typeId, UInt32); + /* Write the value */ + ret |= writeJsonKey(ctx, UA_JSONKEY_VALUE); if(!isArray) { ret |= encodeScalarJsonWrapExtensionObject(ctx, src); } else { - if(ctx->useReversible || !hasDimensions) { - ret |= encodeArrayJsonWrapExtensionObject(ctx, src->data, - src->arrayLength, src->type); - if(hasDimensions) { - ret |= writeJsonKey(ctx, UA_JSONKEY_DIMENSION); - ret |= encodeJsonArray(ctx, src->arrayDimensions, src->arrayDimensionsSize, - &UA_TYPES[UA_TYPES_INT32]); - } - } else { - /* Special case of non-reversible array with dimensions */ - size_t index = 0; - ret |= addMultiArrayContentJSON(ctx, src->data, src->type, &index, - src->arrayDimensions, 0, - src->arrayDimensionsSize); - } + ret |= encodeArrayJsonWrapExtensionObject(ctx, src->data, + src->arrayLength, src->type); } - return ret | writeJsonObjEnd(ctx); + /* Write the dimensions */ + if(hasDimensions) { + ret |= writeJsonKey(ctx, UA_JSONKEY_DIMENSIONS); + ret |= encodeJsonArray(ctx, src->arrayDimensions, src->arrayDimensionsSize, + &UA_TYPES[UA_TYPES_UINT32]); + } + + return ret; +} + +ENCODE_JSON(Variant) { + return writeJsonObjStart(ctx) | encodeVariantInner(ctx, src) | writeJsonObjEnd(ctx); } /* DataValue */ @@ -876,10 +842,8 @@ ENCODE_JSON(DataValue) { status ret = writeJsonObjStart(ctx); - if(hasValue) { - ret |= writeJsonKey(ctx, UA_JSONKEY_VALUE); - ret |= ENCODE_DIRECT_JSON(&src->value, Variant); - } + if(hasValue) + ret |= encodeVariantInner(ctx, &src->value); if(hasStatus) { ret |= writeJsonKey(ctx, UA_JSONKEY_STATUS); @@ -953,20 +917,16 @@ ENCODE_JSON(DiagnosticInfo) { } static status -encodeJsonStructure(CtxJson *ctx, const void *src, const UA_DataType *type) { - status ret = writeJsonObjStart(ctx); - if(ret != UA_STATUSCODE_GOOD) - return ret; - +encodeJsonStructureContent(CtxJson *ctx, const void *src, const UA_DataType *type) { uintptr_t ptr = (uintptr_t) src; u8 membersSize = type->membersSize; + UA_StatusCode ret = UA_STATUSCODE_GOOD; for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) { const UA_DataTypeMember *m = &type->members[i]; const UA_DataType *mt = m->memberType; - - if(m->memberName != NULL && *m->memberName != 0) - ret |= writeJsonKey(ctx, m->memberName); - + if(m->memberName == NULL) + return UA_STATUSCODE_BADENCODINGERROR; + ret |= writeJsonKey(ctx, m->memberName); if(!m->isArray) { ptr += m->padding; size_t memSize = mt->memSize; @@ -980,8 +940,12 @@ encodeJsonStructure(CtxJson *ctx, const void *src, const UA_DataType *type) { ptr += sizeof (void*); } } + return ret; +} - return ret | writeJsonObjEnd(ctx); +static status +encodeJsonStructure(CtxJson *ctx, const void *src, const UA_DataType *type) { + return writeJsonObjStart(ctx) | encodeJsonStructureContent(ctx, src, type) | writeJsonObjEnd(ctx); } static status @@ -1766,21 +1730,16 @@ getExtensionObjectType(ParseCtx *ctx) { /* Check if all array members are ExtensionObjects of the same type. Return this * type or NULL. */ static const UA_DataType * -getArrayUnwrapType(ParseCtx *ctx, size_t arrayIndex) { - UA_assert(ctx->tokens[arrayIndex].type == CJ5_TOKEN_ARRAY); - - /* Save index to restore later */ - size_t oldIndex = ctx->index; - ctx->index = arrayIndex; +getArrayUnwrapType(ParseCtx *ctx) { + UA_assert(ctx->tokens[ctx->index].type == CJ5_TOKEN_ARRAY); /* Return early for empty arrays */ size_t length = (size_t)ctx->tokens[ctx->index].size; - if(length == 0) { - ctx->index = oldIndex; /* Restore the index */ + if(length == 0) return NULL; - } - /* Go to first array member */ + /* Save the original index and go to the first array member */ + size_t oldIndex = ctx->index; ctx->index++; /* Lookup the type for the first array member */ @@ -1792,18 +1751,7 @@ getArrayUnwrapType(ParseCtx *ctx, size_t arrayIndex) { return NULL; } - /* The content is a builtin type that could have been directly encoded in - * the Variant, there was no need to wrap in an ExtensionObject. But this - * means for us, that somebody made an extra effort to explicitly get an - * ExtensionObject. So we keep it. As an added advantage we will generate - * the same JSON again when encoding again. */ - UA_Boolean isBuiltin = (typeOfBody->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); - if(isBuiltin) { - ctx->index = oldIndex; /* Restore the index */ - return NULL; - } - - /* Get the typeId index for faster comparison below. + /* Get the TypeId encoding for faster comparison below. * Cannot fail as getExtensionObjectType already looked this up. */ size_t typeIdIndex = 0; UA_StatusCode ret = lookAheadForKey(ctx, UA_JSONKEY_TYPEID, &typeIdIndex); @@ -1812,7 +1760,8 @@ getArrayUnwrapType(ParseCtx *ctx, size_t arrayIndex) { const char* typeIdData = &ctx->json5[ctx->tokens[typeIdIndex].start]; size_t typeIdSize = getTokenLength(&ctx->tokens[typeIdIndex]); - /* Loop over all members and check whether they can be unwrapped */ + /* Loop over all members and check whether they can be unwrapped. Don't skip + * the first member. We still haven't checked the encoding type. */ for(size_t i = 0; i < length; i++) { /* Array element must be an object */ if(currentTokenType(ctx) != CJ5_TOKEN_OBJECT) { @@ -1870,17 +1819,22 @@ Array_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void **dst, const UA_DataTy return UA_STATUSCODE_BADOUTOFMEMORY; /* Decode array members */ + status ret = UA_STATUSCODE_GOOD; uintptr_t ptr = (uintptr_t)*dst; for(size_t i = 0; i < length; i++) { UA_assert(ctx->tokens[ctx->index].type == CJ5_TOKEN_OBJECT); - - /* Get the body field and decode it */ - DecodeEntry entries[3] = { - {UA_JSONKEY_TYPEID, NULL, NULL, false, NULL}, - {UA_JSONKEY_BODY, (void*)ptr, NULL, false, type}, - {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL} - }; - status ret = decodeFields(ctx, entries, 3); /* Also skips to the next object */ + if(type->typeKind == UA_DATATYPEKIND_STRUCTURE) { + /* Decode structure in-situ in the ExtensionObject */ + ret = decodeJsonStructure(ctx, (void*)ptr, type); + } else { + /* Get the body field and decode it */ + DecodeEntry entries[3] = { + {UA_JSONKEY_TYPEID, NULL, NULL, false, NULL}, + {UA_JSONKEY_BODY, (void*)ptr, NULL, false, type}, + {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL} + }; + ret = decodeFields(ctx, entries, 3); + } if(ret != UA_STATUSCODE_GOOD) { UA_Array_delete(*dst, i+1, type); *dst = NULL; @@ -1894,43 +1848,98 @@ Array_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void **dst, const UA_DataTy } static status -decodeVariantBodyWithType(ParseCtx *ctx, UA_Variant *dst, size_t bodyIndex, - size_t *dimIndex, const UA_DataType *type) { - /* Value is an array? */ - UA_Boolean isArray = (ctx->tokens[bodyIndex].type == CJ5_TOKEN_ARRAY); +decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { + /* Search for the type */ + size_t typeIndex = 0; + status ret = lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); + if(ret != UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADDECODINGERROR; - /* TODO: Handling of null-arrays (length -1) needs to be clarified - * - * if(tokenIsNull(ctx, bodyIndex)) { - * isArray = true; - * dst->arrayLength = 0; - * } */ - - /* No array but has dimension -> error */ - if(!isArray && dimIndex) + /* Parse the type kind */ + if(ctx->tokens[typeIndex].type != CJ5_TOKEN_NUMBER) + return UA_STATUSCODE_BADDECODINGERROR; + UA_UInt64 typeKind = 0; + size_t len = parseUInt64(&ctx->json5[ctx->tokens[typeIndex].start], + getTokenLength(&ctx->tokens[typeIndex]), &typeKind); + if(len == 0) return UA_STATUSCODE_BADDECODINGERROR; /* Get the datatype of the content. The type must be a builtin data type. * All not-builtin types are wrapped in an ExtensionObject. */ - if(type->typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO) + typeKind--; + if(typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO) return UA_STATUSCODE_BADDECODINGERROR; + const UA_DataType *type = &UA_TYPES[typeKind]; - /* A variant cannot contain a variant. But it can contain an array of - * variants */ - if(type->typeKind == UA_DATATYPEKIND_VARIANT && !isArray) - return UA_STATUSCODE_BADDECODINGERROR; + /* Search for the dimensions */ + size_t *dimPtr = NULL; + size_t dimIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_DIMENSIONS, &dimIndex); + if(ret == UA_STATUSCODE_GOOD && ctx->tokens[dimIndex].size > 0) + dimPtr = &dimIndex; + /* Search the value field */ + size_t valueIndex = 0; + size_t beginIndex = ctx->index; + ret = lookAheadForKey(ctx, UA_JSONKEY_VALUE, &valueIndex); + if(ret != UA_STATUSCODE_GOOD || + ctx->tokens[valueIndex].type == CJ5_TOKEN_NULL) { + /* Scalar with dimensions -> error */ + if(dimPtr) + return UA_STATUSCODE_BADDECODINGERROR; + /* Null value */ + dst->data = UA_new(type); + if(!dst->data) + return UA_STATUSCODE_BADOUTOFMEMORY; + dst->type = type; + skipObject(ctx); + return UA_STATUSCODE_GOOD; + } + + /* Value is an array? */ + UA_Boolean isArray = (ctx->tokens[valueIndex].type == CJ5_TOKEN_ARRAY); + + /* Decode the value */ ctx->depth++; - ctx->index = bodyIndex; - - /* Decode an array */ + ctx->index = valueIndex; status res = UA_STATUSCODE_GOOD; - if(isArray) { - /* Try to unwrap ExtensionObjects in the array. - * The members must all have the same type. */ + if(!isArray) { + /* Scalar with dimensions -> error */ + if(dimPtr) { + res = UA_STATUSCODE_BADDECODINGERROR; + goto out; + } + + /* A variant cannot contain a variant. But it can contain an array of + * variants */ + if(type->typeKind == UA_DATATYPEKIND_VARIANT) { + res = UA_STATUSCODE_BADDECODINGERROR; + goto out; + } + + /* Decode a value wrapped in an ExtensionObject */ + if(type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { + res = Variant_decodeJsonUnwrapExtensionObject(ctx, dst, NULL); + goto out; + } + + /* Allocate memory for the value */ + dst->data = UA_new(type); + if(!dst->data) { + res = UA_STATUSCODE_BADOUTOFMEMORY; + goto out; + } + + /* Decode the value */ + dst->type = type; + res = decodeJsonJumpTable[type->typeKind](ctx, dst->data, type); + } else { + /* Decode an array. Try to unwrap ExtensionObjects in the array. The + * members must all have the same type. */ const UA_DataType *unwrapType = NULL; - if(type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT] && - (unwrapType = getArrayUnwrapType(ctx, bodyIndex))) { + if(type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]) + unwrapType = getArrayUnwrapType(ctx); + if(unwrapType) { dst->type = unwrapType; res = Array_decodeJsonUnwrapExtensionObject(ctx, &dst->data, unwrapType); } else { @@ -1939,33 +1948,29 @@ decodeVariantBodyWithType(ParseCtx *ctx, UA_Variant *dst, size_t bodyIndex, } /* Decode array dimensions */ - if(dimIndex) { - ctx->index = *dimIndex; + if(dimPtr) { + ctx->index = *dimPtr; res |= Array_decodeJson(ctx, (void**)&dst->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]); + + /* Validate the dimensions */ + size_t total = 1; + for(size_t i = 0; i < dst->arrayDimensionsSize; i++) + total *= dst->arrayDimensions[i]; + if(total != dst->arrayLength) + res |= UA_STATUSCODE_BADDECODINGERROR; + + /* Only keep >= 2 dimensions */ + if(dst->arrayDimensionsSize == 1) { + UA_free(dst->arrayDimensions); + dst->arrayDimensions = NULL; + dst->arrayDimensionsSize = 0; + } } - ctx->depth--; - return res; } - /* Decode a value wrapped in an ExtensionObject */ - if(type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { - res = Variant_decodeJsonUnwrapExtensionObject(ctx, dst, NULL); - goto out; - } - - /* Allocate Memory for Body */ - dst->data = UA_new(type); - if(!dst->data) { - res = UA_STATUSCODE_BADOUTOFMEMORY; - goto out; - } - - /* Decode the body */ - dst->type = type; - if(ctx->tokens[ctx->index].type != CJ5_TOKEN_NULL) - res = decodeJsonJumpTable[type->typeKind](ctx, dst->data, type); - out: + ctx->index = beginIndex; + skipObject(ctx); ctx->depth--; return res; } @@ -1973,59 +1978,26 @@ decodeVariantBodyWithType(ParseCtx *ctx, UA_Variant *dst, size_t bodyIndex, DECODE_JSON(Variant) { CHECK_NULL_SKIP; /* Treat null as an empty variant */ CHECK_OBJECT; - - /* First search for the variant type in the json object. */ - size_t typeIndex = 0; - status ret = lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); - if(ret != UA_STATUSCODE_GOOD) { - skipObject(ctx); - return UA_STATUSCODE_GOOD; - } - - /* Parse the type */ - if(ctx->tokens[typeIndex].type != CJ5_TOKEN_NUMBER) - return UA_STATUSCODE_BADDECODINGERROR; - UA_UInt64 idType = 0; - size_t len = parseUInt64(&ctx->json5[ctx->tokens[typeIndex].start], - getTokenLength(&ctx->tokens[typeIndex]), &idType); - if(len == 0) - return UA_STATUSCODE_BADDECODINGERROR; - - /* A NULL Variant */ - if(idType == 0) { - skipObject(ctx); - return UA_STATUSCODE_GOOD; - } - - /* Set the type */ - UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idType); - type = UA_findDataTypeWithCustom(&typeNodeId, ctx->customTypes); - if(!type) - return UA_STATUSCODE_BADDECODINGERROR; - - /* Search for body */ - size_t bodyIndex = 0; - ret = lookAheadForKey(ctx, UA_JSONKEY_BODY, &bodyIndex); - if(ret != UA_STATUSCODE_GOOD) - return UA_STATUSCODE_BADDECODINGERROR; - - /* Search for the dimensions */ - size_t *dimPtr = NULL; - size_t dimIndex = 0; - ret = lookAheadForKey(ctx, UA_JSONKEY_DIMENSION, &dimIndex); - if(ret == UA_STATUSCODE_GOOD && ctx->tokens[dimIndex].size > 0) - dimPtr = &dimIndex; - - /* Decode the body */ - return decodeVariantBodyWithType(ctx, dst, bodyIndex, dimPtr, type); + return decodeJSONVariant(ctx, dst); } DECODE_JSON(DataValue) { CHECK_NULL_SKIP; /* Treat a null value as an empty DataValue */ CHECK_OBJECT; - DecodeEntry entries[6] = { - {UA_JSONKEY_VALUE, &dst->value, NULL, false, &UA_TYPES[UA_TYPES_VARIANT]}, + /* Decode the Variant in-situ */ + size_t beginIndex = ctx->index; + status ret = decodeJSONVariant(ctx, &dst->value); + ctx->index = beginIndex; + dst->hasValue = (dst->value.type != NULL); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + /* Decode the other members (skip the Variant members) */ + DecodeEntry entries[8] = { + {UA_JSONKEY_TYPE, NULL, NULL, false, NULL}, + {UA_JSONKEY_VALUE, NULL, NULL, false, NULL}, + {UA_JSONKEY_DIMENSIONS, NULL, NULL, false, NULL}, {UA_JSONKEY_STATUS, &dst->status, NULL, false, &UA_TYPES[UA_TYPES_STATUSCODE]}, {UA_JSONKEY_SOURCETIMESTAMP, &dst->sourceTimestamp, NULL, false, &UA_TYPES[UA_TYPES_DATETIME]}, {UA_JSONKEY_SOURCEPICOSECONDS, &dst->sourcePicoseconds, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, @@ -2033,19 +2005,18 @@ DECODE_JSON(DataValue) { {UA_JSONKEY_SERVERPICOSECONDS, &dst->serverPicoseconds, NULL, false, &UA_TYPES[UA_TYPES_UINT16]} }; - status ret = decodeFields(ctx, entries, 6); - dst->hasValue = entries[0].found; - dst->hasStatus = entries[1].found; - dst->hasSourceTimestamp = entries[2].found; - dst->hasSourcePicoseconds = entries[3].found; - dst->hasServerTimestamp = entries[4].found; - dst->hasServerPicoseconds = entries[5].found; + ret = decodeFields(ctx, entries, 8); + dst->hasStatus = entries[3].found; + dst->hasSourceTimestamp = entries[4].found; + dst->hasSourcePicoseconds = entries[5].found; + dst->hasServerTimestamp = entries[6].found; + dst->hasServerPicoseconds = entries[7].found; return ret; } /* Move the entire current token into the target bytestring */ static UA_StatusCode -tokenToByteString(ParseCtx *ctx, UA_ByteString *p, const UA_DataType *type) { +tokenToByteString(ParseCtx *ctx, UA_ByteString *p) { GET_TOKEN; UA_StatusCode res = UA_ByteString_allocBuffer(p, tokenSize); if(res != UA_STATUSCODE_GOOD) @@ -2055,6 +2026,57 @@ tokenToByteString(ParseCtx *ctx, UA_ByteString *p, const UA_DataType *type) { return UA_STATUSCODE_GOOD; } +/* Remove an unwanted field from an object. The original data is in ctx->json5. + * ctx->index points to the beginning of the object. That object was copied + * verbatim into the encoding ByteString. tokenIndex points to the field (after + * the field name) that shall be removed. */ +static void +removeFieldFromEncoding(ParseCtx *ctx, UA_ByteString *encoding, size_t tokenIndex) { + /* Which part of the encoding to cut out */ + unsigned objStart = ctx->tokens[ctx->index].start; + unsigned objEnd = ctx->tokens[ctx->index].end; + unsigned start = ctx->tokens[tokenIndex-1].start; + unsigned end = ctx->tokens[tokenIndex].end + 1; /* One char after */ + + UA_Boolean haveBefore = (ctx->index < tokenIndex - 2); + if(haveBefore) { + /* Find where the previous token ended. This also removes the comma + * between the previous and the current element. */ + for(size_t i = ctx->index + 2; i < tokenIndex - 1; i++) { + if(ctx->tokens[i].end + 1 > start) + start = ctx->tokens[i].end + 1; + } + if(ctx->json5[start] == '"' || ctx->json5[start] == '\'') + start++; + } else { + /* No previous element. Remove the quoation marks of the field name. */ + start = ctx->tokens[tokenIndex-1].start; + if(start > 0 && (ctx->json5[start-1] == '"' || ctx->json5[start-1] == '\'')) + start--; + + /* Find the beginning of the next field in the object. + * This removes the comma after the current field. */ + size_t oldIndex = ctx->index; + ctx->index = tokenIndex; + skipObject(ctx); + if(ctx->index < ctx->tokensSize && ctx->tokens[ctx->index].start < objEnd) { + end = ctx->tokens[ctx->index].start; + if(ctx->json5[end-1] == '"' || ctx->json5[end-1] == '\'') + end--; + } + ctx->index = oldIndex; + } + + /* Subtract the offset between ctx->json5 end encoding */ + start -= objStart; + end -= objStart; + + /* Cut out the field we want to remove */ + size_t remaining = encoding->length - end; + memmove(encoding->data + start, encoding->data + end, remaining); + encoding->length -= (end - start); +} + DECODE_JSON(ExtensionObject) { CHECK_NULL_SKIP; /* Treat a null value as an empty DataValue */ CHECK_OBJECT; @@ -2065,58 +2087,108 @@ DECODE_JSON(ExtensionObject) { return UA_STATUSCODE_GOOD; } + /* Store the index where the ExtensionObject begins */ + size_t beginIndex = ctx->index; + /* Search for non-JSON encoding */ UA_UInt64 encoding = 0; size_t encIndex = 0; status ret = lookAheadForKey(ctx, UA_JSONKEY_ENCODING, &encIndex); if(ret == UA_STATUSCODE_GOOD) { const char *extObjEncoding = &ctx->json5[ctx->tokens[encIndex].start]; - size_t len = parseUInt64(extObjEncoding, getTokenLength(&ctx->tokens[encIndex]), - &encoding); - if(len == 0) + size_t len = parseUInt64(extObjEncoding, getTokenLength(&ctx->tokens[encIndex]), &encoding); + if(len == 0 || encoding > 2) return UA_STATUSCODE_BADDECODINGERROR; } - /* Lookup the DataType for the ExtensionObject if the body can be decoded */ - const UA_DataType *typeOfBody = (encoding == 0) ? getExtensionObjectType(ctx) : NULL; + /* Get the type NodeId index */ + size_t typeIdIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_TYPEID, &typeIdIndex); + if(ret != UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADDECODINGERROR; - /* Keep the encoded body */ - if(!typeOfBody) { - DecodeEntry entries[3] = { - {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL}, - {UA_JSONKEY_TYPEID, &dst->content.encoded.typeId, NULL, false, &UA_TYPES[UA_TYPES_NODEID]}, - {UA_JSONKEY_BODY, &dst->content.encoded.body, NULL, false, &UA_TYPES[UA_TYPES_STRING]} - }; + /* Decode the type NodeId */ + UA_NodeId typeId; + UA_NodeId_init(&typeId); + ctx->index = (UA_UInt16)typeIdIndex; + ret = NodeId_decodeJson(ctx, &typeId, &UA_TYPES[UA_TYPES_NODEID]); + ctx->index = beginIndex; + if(ret != UA_STATUSCODE_GOOD) { + UA_NodeId_clear(&typeId); /* We don't have the global cleanup */ + return UA_STATUSCODE_BADDECODINGERROR; + } - if(encoding == 0) { - entries[2].function = (decodeJsonSignature)tokenToByteString; - dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; /* ByteString in Json Body */ - } else if(encoding == 1) { - dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; /* ByteString in Json Body */ - } else if(encoding == 2) { - dst->encoding = UA_EXTENSIONOBJECT_ENCODED_XML; /* XmlElement in Json Body */ - } else { - return UA_STATUSCODE_BADDECODINGERROR; + /* Lookup the type */ + type = UA_findDataTypeWithCustom(&typeId, ctx->customTypes); + + /* Unknown body type */ + if(!type) { + /* FIXME: We need UA_EXTENSIONOBJECT_ENCODED_JSON when we parse an + * unknown type in JSON. But it is not defined in the standard. */ + dst->encoding = (encoding != 2) ? + UA_EXTENSIONOBJECT_ENCODED_BYTESTRING : + UA_EXTENSIONOBJECT_ENCODED_XML; + dst->content.encoded.typeId = typeId; + + /* Get the body field index */ + size_t bodyIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_BODY, &bodyIndex); + if(ret != UA_STATUSCODE_GOOD) { + /* Only JSON structures can be encoded in-situ */ + if(encoding != 0) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Extract the entire ExtensionObject object as the body */ + ret = tokenToByteString(ctx, &dst->content.encoded.body); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + /* Remove the UaEncoding and UaTypeId field from the encoding. + * Remove the later field first. */ + if(encIndex != 0 && encIndex > typeIdIndex) + removeFieldFromEncoding(ctx, &dst->content.encoded.body, encIndex); + removeFieldFromEncoding(ctx, &dst->content.encoded.body, typeIdIndex); + if(encIndex != 0 && encIndex < typeIdIndex) + removeFieldFromEncoding(ctx, &dst->content.encoded.body, encIndex); + + return UA_STATUSCODE_GOOD; } - return decodeFields(ctx, entries, 3); + + ctx->index = bodyIndex; + if(encoding != 0) { + /* Decode the body as a ByteString */ + ret = ByteString_decodeJson(ctx, &dst->content.encoded.body, NULL); + } else { + /* Use the JSON encoding directly */ + ret = tokenToByteString(ctx, &dst->content.encoded.body); + } + ctx->index = beginIndex; + skipObject(ctx); + return ret; } + /* No need to keep the TypeId */ + UA_NodeId_clear(&typeId); + /* Allocate memory for the decoded data */ - dst->content.decoded.data = UA_new(typeOfBody); + dst->content.decoded.data = UA_new(type); if(!dst->content.decoded.data) return UA_STATUSCODE_BADOUTOFMEMORY; - - /* Set type */ - dst->content.decoded.type = typeOfBody; + dst->content.decoded.type = type; dst->encoding = UA_EXTENSIONOBJECT_DECODED; - /* Decode body */ - DecodeEntry entries[3] = { - {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL}, - {UA_JSONKEY_TYPEID, NULL, NULL, false, NULL}, - {UA_JSONKEY_BODY, dst->content.decoded.data, NULL, false, typeOfBody} - }; - return decodeFields(ctx, entries, 3); + /* Get the body field index */ + size_t bodyIndex = ctx->index; + ret = lookAheadForKey(ctx, UA_JSONKEY_BODY, &bodyIndex); /* Can fail */ + if(ret == UA_STATUSCODE_GOOD) { + ctx->index = bodyIndex; + ret = decodeJsonJumpTable[type->typeKind](ctx, dst->content.decoded.data, type); + ctx->index = beginIndex; + skipObject(ctx); + return ret; + } + + return decodeJsonJumpTable[type->typeKind](ctx, dst->content.decoded.data, type); } static status diff --git a/tests/check_types_builtin_json.c b/tests/check_types_builtin_json.c index 4946018d9..61f028c57 100644 --- a/tests/check_types_builtin_json.c +++ b/tests/check_types_builtin_json.c @@ -1002,7 +1002,7 @@ START_TEST(UA_LocText_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "\"theText\""; + char* result = "{\"Locale\":\"theLocale\",\"Text\":\"theText\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1806,7 +1806,7 @@ START_TEST(UA_Variant_Bool_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":1,\"Body\":true}"; + char* result = "{\"UaType\":1,\"Value\":true}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1831,7 +1831,7 @@ START_TEST(UA_Variant_Number_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":9,\"Body\":\"345634563456\"}"; + char* result = "{\"UaType\":9,\"Value\":\"345634563456\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -1886,8 +1886,6 @@ START_TEST(UA_Variant_Double2_json_encode) { status retval = UA_encodeJson((void *) src, type, &buf, NULL); - /*{"Type":11,"Body":4.4501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375e-308}*/ - UA_Variant out; UA_Variant_init(&out); retval |= UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -2040,7 +2038,7 @@ START_TEST(UA_Variant_NodeId_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":17,\"Body\":\"ns=1;s=theID\"}"; + char* result = "{\"UaType\":17,\"Value\":\"ns=1;s=theID\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2066,7 +2064,7 @@ START_TEST(UA_Variant_LocText_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":21,\"Body\":{\"Locale\":\"localeString\",\"Text\":\"textString\"}}"; + char* result = "{\"UaType\":21,\"Value\":{\"Locale\":\"localeString\",\"Text\":\"textString\"}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2094,7 +2092,7 @@ START_TEST(UA_Variant_QualName_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":20,\"Body\":\"1:derName\"}"; + char* result = "{\"UaType\":20,\"Value\":\"1:derName\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2111,7 +2109,6 @@ START_TEST(UA_Variant_Array_UInt16_json_encode) { const UA_DataType *type = &UA_TYPES[UA_TYPES_VARIANT]; size_t size = UA_calcSizeJson((void *) src, type, NULL); - ck_assert_uint_eq(size, 25); UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); @@ -2120,7 +2117,7 @@ START_TEST(UA_Variant_Array_UInt16_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":5,\"Body\":[42,43]}"; + char* result = "{\"UaType\":5,\"Value\":[42,43]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2143,7 +2140,7 @@ START_TEST(UA_Variant_Array_UInt16_Null_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":5,\"Body\":[]}"; + char* result = "{\"UaType\":5,\"Value\":[]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); @@ -2168,7 +2165,7 @@ START_TEST(UA_Variant_Array_Byte_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":3,\"Body\":[42,43]}"; + char* result = "{\"UaType\":3,\"Value\":[42,43]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2192,7 +2189,7 @@ START_TEST(UA_Variant_Array_String_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":12,\"Body\":[\"eins\",\"zwei\"]}"; + char* result = "{\"UaType\":12,\"Value\":[\"eins\",\"zwei\"]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2221,15 +2218,13 @@ START_TEST(UA_Variant_Matrix_UInt16_json_encode) { UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); - //{"Type":5,"Body":[1,2,3,4,5,6,7,8,9],"Dimension":[3,3]} size_t sizeOfBytes = UA_calcSizeJson((void *) &src, type, NULL); - ck_assert_uint_eq(sizeOfBytes, 55); status s = UA_encodeJson((void *) &src, type, &buf, NULL); ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":5,\"Body\":[1,2,3,4,5,6,7,8,9],\"Dimension\":[3,3]}"; + char* result = "{\"UaType\":5,\"Value\":[1,2,3,4,5,6,7,8,9],\"Dimensions\":[3,3]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2255,7 +2250,7 @@ START_TEST(UA_Variant_StatusCode_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":{\"Code\":2161770496,\"Symbol\":\"BadAggregateConfigurationRejected\"}}"; + char* result = "{\"UaType\":19,\"Value\":{\"Code\":2161770496,\"Symbol\":\"BadAggregateConfigurationRejected\"}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2289,7 +2284,7 @@ START_TEST(UA_Variant_Array_String_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"]}"; + char* result = "{\"UaType\":12,\"Value\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2317,8 +2312,6 @@ START_TEST(UA_Variant_Matrix_String_NonReversible_json_encode) { UA_EncodeJsonOptions options = {0}; /* non reversible */ size_t size = UA_calcSizeJson((void *) &src, type, &options); - //{"Body":[[[["1"],["2"]],[["3"],["4"]]],[[["5"],["6"]],[["7"],["8"]]]]} - ck_assert_uint_eq(size, 70); UA_ByteString buf; UA_ByteString_allocBuffer(&buf, size+1); @@ -2327,7 +2320,7 @@ START_TEST(UA_Variant_Matrix_String_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[[[[\"1\"],[\"2\"]],[[\"3\"],[\"4\"]]],[[[\"5\"],[\"6\"]],[[\"7\"],[\"8\"]]]]}"; + char* result = "{\"UaType\":12,\"Value\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"],\"Dimensions\":[2,2,2,1]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2360,7 +2353,7 @@ START_TEST(UA_Variant_Matrix_NodeId_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[[[[\"ns=1;i=1\"],[\"ns=1;i=2\"]],[[\"ns=1;i=3\"],[\"ns=1;i=4\"]]],[[[\"ns=1;i=5\"],[\"ns=1;i=6\"]],[[\"ns=1;i=7\"],[\"ns=1;i=8\"]]]]}"; + char* result = "{\"UaType\":17,\"Value\":[\"ns=1;i=1\",\"ns=1;i=2\",\"ns=1;i=3\",\"ns=1;i=4\",\"ns=1;i=5\",\"ns=1;i=6\",\"ns=1;i=7\",\"ns=1;i=8\"],\"Dimensions\":[2,2,2,1]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2389,7 +2382,7 @@ START_TEST(UA_Variant_Wrap_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":22,\"Body\":{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}}}"; + char* result = "{\"UaType\":22,\"Value\":{\"UaTypeId\":\"i=511\",\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1236}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2431,7 +2424,7 @@ START_TEST(UA_Variant_Wrap_Array_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Type\":22,\"Body\":[{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; + char* result = "{\"UaType\":22,\"Value\":[{\"UaTypeId\":\"i=511\",\"ViewId\":\"i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1},{\"UaTypeId\":\"i=511\",\"ViewId\":\"i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2473,7 +2466,7 @@ START_TEST(UA_Variant_Wrap_Array_NonReversible_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":[{\"Body\":{\"ViewId\":\"ns=1;i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1}},{\"Body\":{\"ViewId\":\"ns=1;i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}}]}"; + char* result = "{\"UaType\":22,\"Value\":[{\"UaTypeId\":\"i=511\",\"ViewId\":\"ns=1;i=1\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":1},{\"UaTypeId\":\"i=511\",\"ViewId\":\"ns=1;i=2\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"ViewVersion\":2}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2501,7 +2494,7 @@ START_TEST(UA_ExtensionObject_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":\"i=1\",\"Body\":false}"; + char* result = "{\"UaTypeId\":\"i=1\",\"UaBody\":false}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2528,7 +2521,7 @@ START_TEST(UA_ExtensionObject_xml_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":\"ns=2;i=1234\",\"Encoding\":2,\"Body\":\"\"}"; + char* result = "{\"UaTypeId\":\"ns=2;i=1234\",\"UaEncoding\":2,\"UaBody\":\"\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2556,7 +2549,7 @@ START_TEST(UA_ExtensionObject_byteString_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"TypeId\":\"ns=2;i=1234\",\"Encoding\":1,\"Body\":\"123456789012345678901234567890\"}"; + char* result = "{\"UaTypeId\":\"ns=2;i=1234\",\"UaEncoding\":1,\"UaBody\":\"123456789012345678901234567890\"}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2585,7 +2578,7 @@ START_TEST(UA_ExtensionObject_NonReversible_StatusCode_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Body\":{\"Code\":2147876864,\"Symbol\":\"BadEncodingError\"}}"; + char* result = "{\"UaTypeId\":\"i=19\",\"UaBody\":{\"Code\":2147876864,\"Symbol\":\"BadEncodingError\"}}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2783,7 +2776,7 @@ START_TEST(UA_DataValue_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07.8901234Z\",\"SourcePicoseconds\":5678,\"ServerTimestamp\":\"1970-01-28T03:34:38.9012345Z\",\"ServerPicoseconds\":6789}"; + char* result = "{\"UaType\":1,\"Value\":true,\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07.8901234Z\",\"SourcePicoseconds\":5678,\"ServerTimestamp\":\"1970-01-28T03:34:38.9012345Z\",\"ServerPicoseconds\":6789}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -2904,7 +2897,7 @@ START_TEST(UA_MessageReadResponse_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":{},\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"Results\":[{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; + char* result = "{\"ResponseHeader\":{\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ServiceResult\":{},\"ServiceDiagnostics\":{\"AdditionalInfo\":\"serverDiag\"},\"StringTable\":[],\"AdditionalHeader\":{\"UaTypeId\":\"i=1\",\"UaBody\":false}},\"Results\":[{\"UaType\":1,\"Value\":true,\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}],\"DiagnosticInfos\":[{\"AdditionalInfo\":\"INNER ADDITION INFO\"}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -3038,7 +3031,7 @@ START_TEST(UA_WriteRequest_json_encode) { ck_assert_int_eq(s, UA_STATUSCODE_GOOD); // then - char* result = "{\"RequestHeader\":{\"AuthenticationToken\":\"s=authToken\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"TypeId\":\"i=1\",\"Body\":false}},\"NodesToWrite\":[{\"NodeId\":\"s=a1111\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":\"s=a2222\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; + char* result = "{\"RequestHeader\":{\"AuthenticationToken\":\"s=authToken\",\"Timestamp\":\"1970-01-15T06:56:07Z\",\"RequestHandle\":123123,\"ReturnDiagnostics\":1,\"AuditEntryId\":\"Auditentryid\",\"TimeoutHint\":120,\"AdditionalHeader\":{\"UaTypeId\":\"i=1\",\"UaBody\":false}},\"NodesToWrite\":[{\"NodeId\":\"s=a1111\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"UaType\":1,\"Value\":true,\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}},{\"NodeId\":\"s=a2222\",\"AttributeId\":12,\"IndexRange\":\"BLOAB\",\"Value\":{\"UaType\":1,\"Value\":true,\"Status\":{\"Code\":2153250816,\"Symbol\":\"BadApplicationSignatureInvalid\"},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerTimestamp\":\"1970-01-15T06:56:07Z\"}}]}"; buf.data[size] = 0; /* zero terminate */ ck_assert_str_eq(result, (char*)buf.data); UA_ByteString_clear(&buf); @@ -3082,7 +3075,7 @@ START_TEST(UA_VariableAttributes_json_encode) { "\"DisplayName\":{\"Locale\":\"en-US\",\"Text\":\"the answer\"}," "\"Description\":{\"Locale\":\"en-US\",\"Text\":\"the answer\"}," "\"WriteMask\":0,\"UserWriteMask\":0," - "\"Value\":{\"Type\":6,\"Body\":42}," + "\"Value\":{\"UaType\":6,\"Value\":42}," "\"DataType\":\"i=6\",\"ValueRank\":-2," "\"ArrayDimensions\":[]," "\"IsAbstract\":true}"; @@ -3097,7 +3090,7 @@ END_TEST START_TEST(UA_Byte_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":3,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":3,\"Value\":0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3113,7 +3106,7 @@ END_TEST START_TEST(UA_Byte_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":3,\"Body\":255}"); + UA_ByteString buf = UA_STRING("{\"UaType\":3,\"Value\":255}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3131,7 +3124,7 @@ END_TEST START_TEST(UA_UInt16_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":5,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":5,\"Value\":0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3147,7 +3140,7 @@ END_TEST START_TEST(UA_UInt16_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":5,\"Body\":65535}"); + UA_ByteString buf = UA_STRING("{\"UaType\":5,\"Value\":65535}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3164,7 +3157,7 @@ END_TEST START_TEST(UA_UInt32_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":7,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":7,\"Value\":0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3180,7 +3173,7 @@ END_TEST START_TEST(UA_UInt32_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":7,\"Body\":4294967295}"); + UA_ByteString buf = UA_STRING("{\"UaType\":7,\"Value\":4294967295}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3197,7 +3190,7 @@ END_TEST START_TEST(UA_UInt64_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":9,\"Body\":\"0\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":9,\"Value\":\"0\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3244,7 +3237,7 @@ END_TEST START_TEST(UA_UInt64_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":9,\"Body\":\"18446744073709551615\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":9,\"Value\":\"18446744073709551615\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3267,7 +3260,7 @@ END_TEST START_TEST(UA_UInt64_Overflow_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":9,\"Body\":\"18446744073709551616\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":9,\"Value\":\"18446744073709551616\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3281,7 +3274,7 @@ END_TEST START_TEST(UA_SByte_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":2,\"Body\":-128}"); + UA_ByteString buf = UA_STRING("{\"UaType\":2,\"Value\":-128}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3297,7 +3290,7 @@ END_TEST START_TEST(UA_SByte_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":2,\"Body\":127}"); + UA_ByteString buf = UA_STRING("{\"UaType\":2,\"Value\":127}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3314,7 +3307,7 @@ END_TEST START_TEST(UA_Int16_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":4,\"Body\":-32768}"); + UA_ByteString buf = UA_STRING("{\"UaType\":4,\"Value\":-32768}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3330,7 +3323,7 @@ END_TEST START_TEST(UA_Int16_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":4,\"Body\":32767}"); + UA_ByteString buf = UA_STRING("{\"UaType\":4,\"Value\":32767}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3347,7 +3340,7 @@ END_TEST START_TEST(UA_Int32_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":6,\"Body\":-2147483648}"); + UA_ByteString buf = UA_STRING("{\"UaType\":6,\"Value\":-2147483648}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3363,7 +3356,7 @@ END_TEST START_TEST(UA_Int32_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":6,\"Body\":2147483647}"); + UA_ByteString buf = UA_STRING("{\"UaType\":6,\"Value\":2147483647}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3380,7 +3373,7 @@ END_TEST START_TEST(UA_Int64_Min_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":8,\"Body\":\"-9223372036854775808\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":8,\"Value\":\"-9223372036854775808\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3404,7 +3397,7 @@ END_TEST START_TEST(UA_Int64_Max_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":8,\"Body\":\"9223372036854775807\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":8,\"Value\":\"9223372036854775807\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3428,7 +3421,7 @@ END_TEST START_TEST(UA_Int64_Overflow_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":8,\"Body\":\"9223372036854775808\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":8,\"Value\":\"9223372036854775808\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3442,7 +3435,7 @@ END_TEST START_TEST(UA_Int64_TooBig_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":8,\"Body\":\"111111111111111111111111111111\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":8,\"Value\":\"111111111111111111111111111111\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3456,7 +3449,7 @@ END_TEST START_TEST(UA_Int64_NoDigit_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":8,\"Body\":\"a\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":8,\"Value\":\"a\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3470,7 +3463,7 @@ END_TEST START_TEST(UA_Float_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":3.1415927410}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":3.1415927410}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3488,7 +3481,7 @@ END_TEST START_TEST(UA_Float_json_one_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":1}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":1}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3508,7 +3501,7 @@ END_TEST START_TEST(UA_Float_json_inf_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":\"Infinity\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":\"Infinity\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3528,7 +3521,7 @@ END_TEST START_TEST(UA_Float_json_neginf_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":\"-Infinity\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":\"-Infinity\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3547,7 +3540,7 @@ END_TEST START_TEST(UA_Float_json_nan_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":\"NaN\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":\"NaN\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3568,7 +3561,7 @@ END_TEST START_TEST(UA_Float_json_negnan_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":10,\"Body\":\"-NaN\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":10,\"Value\":\"-NaN\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3589,7 +3582,7 @@ END_TEST START_TEST(UA_Double_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":1.1234}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":1.1234}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3611,7 +3604,7 @@ END_TEST START_TEST(UA_Double_corrupt_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":1.12.34}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":1.12.34}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3625,8 +3618,7 @@ END_TEST START_TEST(UA_Double_one_json_decode) { UA_Variant out; UA_Variant_init(&out); - //UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":1}"); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":1}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":1}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3651,7 +3643,7 @@ END_TEST START_TEST(UA_Double_onepointsmallest_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":1.0000000000000002}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":1.0000000000000002}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3675,7 +3667,7 @@ END_TEST START_TEST(UA_Double_nan_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":\"NaN\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":\"NaN\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3690,7 +3682,7 @@ END_TEST START_TEST(UA_Double_negnan_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":\"-NaN\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":\"-NaN\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); // then @@ -3705,7 +3697,7 @@ END_TEST START_TEST(UA_Double_inf_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":\"Infinity\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":\"Infinity\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3728,7 +3720,7 @@ END_TEST START_TEST(UA_Double_neginf_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":\"-Infinity\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":\"-Infinity\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3751,7 +3743,7 @@ END_TEST START_TEST(UA_Double_zero_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3774,7 +3766,7 @@ END_TEST START_TEST(UA_Double_negzero_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":-0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":-0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3797,7 +3789,7 @@ END_TEST START_TEST(UA_String_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"abcdef\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"abcdef\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3819,7 +3811,7 @@ END_TEST START_TEST(UA_String_empty_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3836,7 +3828,7 @@ END_TEST START_TEST(UA_String_unescapeBS_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"ab\\tcdef\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"ab\\tcdef\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3859,7 +3851,7 @@ END_TEST START_TEST(UA_String_escape_unicode_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"\\u002c#\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"\\u002c#\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3878,7 +3870,7 @@ END_TEST START_TEST(UA_String_escape2_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"\\b\\th\\\"e\\fl\\nl\\\\o\\r\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"\\b\\th\\\"e\\fl\\nl\\\\o\\r\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3907,7 +3899,7 @@ END_TEST START_TEST(UA_String_surrogatePair_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":\"\\uD800\\uDC00\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":\"\\uD800\\uDC00\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3928,7 +3920,7 @@ END_TEST START_TEST(UA_ByteString_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":15,\"Body\":\"YXNkZmFzZGY=\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":15,\"Value\":\"YXNkZmFzZGY=\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -3967,7 +3959,7 @@ END_TEST START_TEST(UA_ByteString_null_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":15,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":15,\"Value\":null}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_BYTESTRING); @@ -3980,7 +3972,7 @@ END_TEST START_TEST(UA_Guid_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":14,\"Body\":\"00000001-0002-0003-0405-060708090A0B\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":14,\"Value\":\"00000001-0002-0003-0405-060708090A0B\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4006,7 +3998,7 @@ END_TEST START_TEST(UA_Guid_lower_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":14,\"Body\":\"00000001-0002-0003-0405-060708090a0b\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":14,\"Value\":\"00000001-0002-0003-0405-060708090a0b\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4033,7 +4025,7 @@ END_TEST START_TEST(UA_Guid_tooShort_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":14,\"Body\":\"00000001-00\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":14,\"Value\":\"00000001-00\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4047,7 +4039,7 @@ END_TEST START_TEST(UA_Guid_tooLong_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":14,\"Body\":\"00000001-0002-0003-0405-060708090A0B00000001\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":14,\"Value\":\"00000001-0002-0003-0405-060708090A0B00000001\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4061,7 +4053,7 @@ END_TEST START_TEST(UA_Guid_wrong_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":14,\"Body\":\"00000=01-0002-0003-0405-060708090A0B\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":14,\"Value\":\"00000=01-0002-0003-0405-060708090A0B\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4076,7 +4068,7 @@ END_TEST START_TEST(UA_StatusCode_2_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":{\"Code\":2}}"); + UA_ByteString buf = UA_STRING("{\"UaType\":19,\"Value\":{\"Code\":2}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4093,7 +4085,7 @@ START_TEST(UA_StatusCode_3_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":222222222222222222222222222222222222}"); + UA_ByteString buf = UA_STRING("{\"UaType\":19,\"Value\":222222222222222222222222222222222222}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4107,7 +4099,7 @@ END_TEST START_TEST(UA_StatusCode_0_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":19,\"Body\":{}}"); + UA_ByteString buf = UA_STRING("{\"UaType\":19,\"Value\":{}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4306,7 +4298,7 @@ END_TEST START_TEST(UA_QualifiedName_null_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":20,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":20,\"Value\":null}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_QUALIFIEDNAME); @@ -4356,7 +4348,7 @@ END_TEST START_TEST(UA_LocalizedText_null_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":21,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":21,\"Value\":null}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_LOCALIZEDTEXT); @@ -4697,7 +4689,7 @@ END_TEST START_TEST(UA_DiagnosticInfo_null_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":25,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":25,\"Value\":null}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_DIAGNOSTICINFO); @@ -4711,7 +4703,7 @@ START_TEST(UA_DataValue_json_decode) { UA_DataValue out; UA_DataValue_init(&out); - UA_ByteString buf = UA_STRING("{\"Value\":{\"Type\":1,\"Body\":true},\"Status\":{\"Code\":2153250816},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"SourcePicoseconds\":0,\"ServerTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerPicoseconds\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":true,\"Status\":{\"Code\":2153250816},\"SourceTimestamp\":\"1970-01-15T06:56:07Z\",\"SourcePicoseconds\":0,\"ServerTimestamp\":\"1970-01-15T06:56:07Z\",\"ServerPicoseconds\":0}"); // when @@ -4738,7 +4730,7 @@ START_TEST(UA_DataValueMissingFields_json_decode) { UA_DataValue out; UA_DataValue_init(&out); - UA_ByteString buf = UA_STRING("{\"Value\":{\"Type\":1,\"Body\":true}}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":true}"); // when @@ -4760,7 +4752,7 @@ END_TEST START_TEST(UA_DataValue_null_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":23,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":23,\"Value\":null}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); UA_Variant_clear(&out); @@ -4773,7 +4765,7 @@ START_TEST(UA_ExtensionObject_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"TypeId\":\"i=1\",\"Body\":true}"); + UA_ByteString buf = UA_STRING("{\"UaTypeId\":\"i=1\",\"UaBody\":true}"); // when @@ -4792,7 +4784,7 @@ START_TEST(UA_ExtensionObject_EncodedByteString_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"Encoding\":1,\"TypeId\":\"i=42\",\"Body\":\"YXNkZmFzZGY=\"}"); + UA_ByteString buf = UA_STRING("{\"UaEncoding\":1,\"UaTypeId\":\"i=42\",\"UaBody\":\"YXNkZmFzZGY=\"}"); // when @@ -4800,15 +4792,10 @@ START_TEST(UA_ExtensionObject_EncodedByteString_json_decode) { // then ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); ck_assert_int_eq(out.encoding, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING); - //TODO: Not base64 decoded, correct? - ck_assert_int_eq(out.content.encoded.body.data[0], 'Y'); - ck_assert_int_eq(out.content.encoded.body.data[0], 'Y'); - ck_assert_int_eq(out.content.encoded.body.data[1], 'X'); - ck_assert_int_eq(out.content.encoded.body.data[2], 'N'); - ck_assert_int_eq(out.content.encoded.body.data[3], 'k'); - ck_assert_int_eq(out.content.encoded.body.data[4], 'Z'); - ck_assert_int_eq(out.content.encoded.body.data[5], 'm'); - ck_assert_int_eq(out.content.encoded.body.data[6], 'F'); + ck_assert_int_eq(out.content.encoded.body.data[0], 'a'); + ck_assert_int_eq(out.content.encoded.body.data[1], 's'); + ck_assert_int_eq(out.content.encoded.body.data[2], 'd'); + ck_assert_int_eq(out.content.encoded.body.data[3], 'f'); ck_assert_int_eq(out.content.encoded.typeId.identifier.numeric, 42); UA_ExtensionObject_clear(&out); @@ -4820,7 +4807,7 @@ START_TEST(UA_ExtensionObject_EncodedXml_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"Encoding\":2,\"TypeId\":\"i=42\",\"Body\":\"\"}"); + UA_ByteString buf = UA_STRING("{\"UaEncoding\":2,\"UaTypeId\":\"i=42\",\"UaBody\":\"PEVsZW1lbnQ+PC9FbGVtZW50Pgo=\"}"); // when @@ -4842,7 +4829,7 @@ START_TEST(UA_ExtensionObject_Unkown_json_decode) { UA_ExtensionObject out; UA_ExtensionObject_init(&out); - UA_ByteString buf = UA_STRING("{\"TypeId\":\"i=4711\",\"Body\":{\"unknown\":\"body\",\"saveas\":\"Bytestring\"}}"); + UA_ByteString buf = UA_STRING("{\"UaTypeId\":\"i=4711\",\"UaBody\":{\"unknown\":\"body\",\"saveas\":\"Bytestring\"}}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], NULL); @@ -4869,7 +4856,7 @@ START_TEST(UA_VariantBool_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1,\"Body\":false}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":false}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4886,7 +4873,7 @@ START_TEST(UA_VariantBoolNull_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1,\"Body\":null}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":null}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -4900,9 +4887,10 @@ END_TEST START_TEST(UA_VariantNull_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1}"); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); + UA_Variant_clear(&out); } END_TEST @@ -4911,7 +4899,7 @@ START_TEST(UA_VariantStringArray_json_decode) { UA_Variant *out = UA_Variant_new(); UA_Variant_init(out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"],\"Dimension\":[2,4]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"],\"Dimensions\":[2,4]}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -4943,7 +4931,7 @@ START_TEST(UA_VariantStringArrayNull_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":[null, null, null, null, null, null, null, null],\"Dimension\":[2,4]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":[null, null, null, null, null, null, null, null],\"Dimensions\":[2,4]}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -4977,7 +4965,7 @@ START_TEST(UA_VariantLocalizedTextArrayNull_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":21,\"Body\":[null, null, null, null, null, null, null, null],\"Dimension\":[2,4]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":21,\"Value\":[null, null, null, null, null, null, null, null],\"Dimensions\":[2,4]}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -5010,7 +4998,7 @@ START_TEST(UA_VariantVariantArrayNull_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[null, null, null, null, null, null, null, null],\"Dimension\":[2,4]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":22,\"Value\":[null, null, null, null, null, null, null, null],\"Dimensions\":[2,4]}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -5038,7 +5026,7 @@ START_TEST(UA_VariantVariantArrayEmpty_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":22,\"Value\":[]}"); // when @@ -5055,7 +5043,7 @@ START_TEST(UA_VariantStringArray_WithoutDimension_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5084,7 +5072,7 @@ START_TEST(UA_Variant_BooleanArray_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1,\"Body\":[true, false, true]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":[true, false, true]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5108,14 +5096,14 @@ START_TEST(UA_Variant_ExtensionObjectArray_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":\"i=1\",\"Body\":false}, {\"TypeId\":\"i=1\",\"Body\":true}]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":22,\"Value\":[{\"UaTypeId\":\"i=1\",\"UaBody\":false}, {\"UaTypeId\":\"i=1\",\"UaBody\":true}]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); // don't unwrap builtin types that shouldn't be wrapped in the first place ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_EXTENSIONOBJECT); + ck_assert_int_eq(out.type->typeKind, UA_DATATYPEKIND_BOOLEAN); ck_assert_uint_eq(out.arrayDimensionsSize, 0); ck_assert_uint_eq(out.arrayLength, 2); UA_Variant_clear(&out); @@ -5126,7 +5114,7 @@ START_TEST(UA_Variant_MixedExtensionObjectArray_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":[{\"TypeId\":\"i=1\",\"Body\":false}, {\"TypeId\":\"i=2\",\"Body\":1}]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":22,\"Value\":[{\"UaTypeId\":\"i=1\",\"UaBody\":false}, {\"UaTypeId\":\"i=2\",\"UaBody\":1}]}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5145,7 +5133,7 @@ START_TEST(UA_Variant_bad1_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1,\"Body\":\"\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":\"\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5160,7 +5148,7 @@ START_TEST(UA_Variant_ExtensionObjectWrap_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":22,\"Body\":{\"TypeId\":\"i=511\",\"Body\":{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07.000Z\",\"ViewVersion\":1236}}}"); + UA_ByteString buf = UA_STRING("{\"UaType\":22,\"Value\":{\"UaTypeId\":\"i=511\",\"UaBody\":{\"ViewId\":\"i=99999\",\"Timestamp\":\"1970-01-15T06:56:07.000Z\",\"ViewVersion\":1236}}}"); // when @@ -5176,25 +5164,11 @@ START_TEST(UA_Variant_ExtensionObjectWrap_json_decode) { } END_TEST -START_TEST(UA_duplicate_json_decode) { - // given - UA_Variant out; - UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1, \"Body\":false, \"Type\":1}"); - // when - - UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); - // then - ck_assert_int_eq(retval, UA_STATUSCODE_BADDECODINGERROR); - UA_Variant_clear(&out); -} -END_TEST - START_TEST(UA_wrongBoolean_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1, \"Body\":\"asdfaaaaaaaaaaaaaaaaaaaa\"}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1, \"Value\":\"asdfaaaaaaaaaaaaaaaaaaaa\"}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_BADDECODINGERROR); @@ -5239,7 +5213,7 @@ START_TEST(UA_VariantStringArrayBad_shouldFreeArray_json_decode) { UA_Variant out; UA_Variant_init(&out); //not a string V - UA_ByteString buf = UA_STRING("{\"Type\":12,\"Body\":[\"1\",\"2\",true,\"4\",\"5\",\"6\",\"7\",\"8\"],\"Dimension\":[2,4]}"); + UA_ByteString buf = UA_STRING("{\"UaType\":12,\"Value\":[\"1\",\"2\",true,\"4\",\"5\",\"6\",\"7\",\"8\"],\"Dimensions\":[2,4]}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -5257,7 +5231,7 @@ START_TEST(UA_VariantFuzzer1_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("\\x0a{\"Type\",\"Bode\",\"Body\":{\"se\":}}"); + UA_ByteString buf = UA_STRING("\\x0a{\"UaType\",\"Bode\",\"Value\":{\"se\":}}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -5278,7 +5252,7 @@ START_TEST(UA_VariantFuzzer2_json_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":11,\"Body\":2E+}"); + UA_ByteString buf = UA_STRING("{\"UaType\":11,\"Value\":2E+}"); //UA_ByteString buf = UA_STRING("{\"SymbolicId\":13,\"LocalizedText\":14,\"Locale\":12,\"AdditionalInfo\":\"additionalInfo\",\"InnerStatusCode\":2155216896}"); // when @@ -5293,7 +5267,7 @@ END_TEST START_TEST(UA_Variant_Bad_Type_decode) { UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1000,\"Body\":0}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1000,\"Value\":0}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5309,7 +5283,7 @@ START_TEST(UA_Variant_Bad_Type2_decode) { UA_Variant out; UA_Variant_init(&out); char str[80]; - sprintf(str, "{\"Type\":%d}", i); + sprintf(str, "{\"UaType\":%d}", i); UA_ByteString buf = UA_STRING(str); // when @@ -5327,7 +5301,7 @@ START_TEST(UA_Variant_Malformed_decode) { UA_Variant out; UA_Variant_init(&out); char str[80]; - sprintf(str, "{\"Type\":%d, \"Body:\"}", i); + sprintf(str, "{\"UaType\":%d, \"Value:\"}", i); UA_ByteString buf = UA_STRING(str); UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); ck_assert_int_eq(retval, UA_STATUSCODE_BADDECODINGERROR); @@ -5340,7 +5314,7 @@ START_TEST(UA_Variant_Malformed2_decode) { UA_Variant out; UA_Variant_init(&out); char str[80]; - sprintf(str, "{\"Type\":, \"Body:\"}"); + sprintf(str, "{\"UaType\":, \"Value:\"}"); UA_ByteString buf = UA_STRING(str); // when @@ -5376,7 +5350,7 @@ START_TEST(UA_VariantBool_public_json_decode) { // given UA_Variant out; UA_Variant_init(&out); - UA_ByteString buf = UA_STRING("{\"Type\":1,\"Body\":false}"); + UA_ByteString buf = UA_STRING("{\"UaType\":1,\"Value\":false}"); // when UA_StatusCode retval = UA_decodeJson(&buf, &out, &UA_TYPES[UA_TYPES_VARIANT], NULL); @@ -5625,8 +5599,6 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_UInt64_Max_json_decode); tcase_add_test(tc_json_decode, UA_UInt64_Overflow_json_decode); - - tcase_add_test(tc_json_decode, UA_Float_json_decode); tcase_add_test(tc_json_decode, UA_Float_json_one_decode); @@ -5635,7 +5607,6 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_Float_json_nan_decode); tcase_add_test(tc_json_decode, UA_Float_json_negnan_decode); - tcase_add_test(tc_json_decode, UA_Double_json_decode); tcase_add_test(tc_json_decode, UA_Double_one_json_decode); tcase_add_test(tc_json_decode, UA_Double_corrupt_json_decode); @@ -5648,14 +5619,11 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_Double_inf_json_decode); tcase_add_test(tc_json_decode, UA_Double_neginf_json_decode); - //String tcase_add_test(tc_json_decode, UA_String_json_decode); tcase_add_test(tc_json_decode, UA_String_empty_json_decode); tcase_add_test(tc_json_decode, UA_String_unescapeBS_json_decode); - tcase_add_test(tc_json_decode, UA_String_escape_unicode_json_decode); - tcase_add_test(tc_json_decode, UA_String_escape2_json_decode); tcase_add_test(tc_json_decode, UA_String_surrogatePair_json_decode); @@ -5664,7 +5632,6 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_ByteString_bad_json_decode); tcase_add_test(tc_json_decode, UA_ByteString_null_json_decode); - //DateTime tcase_add_test(tc_json_decode, UA_DateTime_json_decode); tcase_add_test(tc_json_decode, UA_DateTime_json_decode_large); @@ -5673,7 +5640,6 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_DateTime_json_decode_max); tcase_add_test(tc_json_decode, UA_DateTime_micro_json_decode); - //Guid tcase_add_test(tc_json_decode, UA_Guid_json_decode); tcase_add_test(tc_json_decode, UA_Guid_lower_json_decode); @@ -5681,24 +5647,20 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_Guid_tooLong_json_decode); tcase_add_test(tc_json_decode, UA_Guid_wrong_json_decode); - //StatusCode tcase_add_test(tc_json_decode, UA_StatusCode_2_json_decode); tcase_add_test(tc_json_decode, UA_StatusCode_3_json_decode); tcase_add_test(tc_json_decode, UA_StatusCode_0_json_decode); - //QualName tcase_add_test(tc_json_decode, UA_QualifiedName_json_decode); tcase_add_test(tc_json_decode, UA_QualifiedName_null_json_decode); - //LocalizedText tcase_add_test(tc_json_decode, UA_LocalizedText_json_decode); tcase_add_test(tc_json_decode, UA_LocalizedText_missing_json_decode); tcase_add_test(tc_json_decode, UA_LocalizedText_null_json_decode); - //-NodeId- tcase_add_test(tc_json_decode, UA_NodeId_Nummeric_json_decode); tcase_add_test(tc_json_decode, UA_NodeId_Nummeric_Namespace_json_decode); @@ -5718,7 +5680,6 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_DiagnosticInfo_json_decode); tcase_add_test(tc_json_decode, UA_DiagnosticInfo_null_json_decode); - //Variant tcase_add_test(tc_json_decode, UA_VariantBool_json_decode); tcase_add_test(tc_json_decode, UA_VariantBoolNull_json_decode); @@ -5746,29 +5707,21 @@ static Suite *testSuite_builtin_json(void) { tcase_add_test(tc_json_decode, UA_ExtensionObject_EncodedXml_json_decode); tcase_add_test(tc_json_decode, UA_ExtensionObject_Unkown_json_decode); - //Others - tcase_add_test(tc_json_decode, UA_duplicate_json_decode); tcase_add_test(tc_json_decode, UA_wrongBoolean_json_decode); - tcase_add_test(tc_json_decode, UA_ViewDescription_json_decode); tcase_add_test(tc_json_decode, UA_DataTypeAttributes_json_decode); - - tcase_add_test(tc_json_decode, UA_VariantStringArrayBad_shouldFreeArray_json_decode); tcase_add_test(tc_json_decode, UA_VariantFuzzer1_json_decode); tcase_add_test(tc_json_decode, UA_VariantFuzzer2_json_decode); - tcase_add_test(tc_json_decode, UA_Variant_Bad_Type_decode); tcase_add_test(tc_json_decode, UA_Variant_Bad_Type2_decode); - tcase_add_test(tc_json_decode, UA_Variant_Malformed_decode); tcase_add_test(tc_json_decode, UA_Variant_Malformed2_decode); // public api tcase_add_test(tc_json_decode, UA_VariantBool_public_json_decode); tcase_add_test(tc_json_decode, UA_Boolean_true_public_json_encode); - suite_add_tcase(s, tc_json_decode); TCase *tc_json_helper = tcase_create("json_helper"); From e875bbd0589aae58db9ef45c8d7b595c221bedce Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 20:16:34 +0100 Subject: [PATCH 102/158] feat(pubsub): Added placeholder TODOs for missing JSON fields --- src/pubsub/ua_pubsub_networkmessage_json.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index d1dcc1c09..adf8d0cae 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -46,6 +46,12 @@ UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, if(rv != UA_STATUSCODE_GOOD) return rv; + /* TODO: Encode DataSetWriterName */ + + /* TODO: Encode PublisherId (omitted if in the NetworkMessage header) */ + + /* TODO: Encode WriterGroupName (omitted if in the NetworkMessage header) */ + /* DataSetMessageSequenceNr */ if(src->header.dataSetMessageSequenceNrEnabled) { rv |= writeJsonObjElm(ctx, UA_DECODEKEY_SEQUENCENUMBER, @@ -67,6 +73,8 @@ UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, return rv; } + /* TODO: MinorVersion (omitted if the MetaDataVersion is sent) */ + /* Timestamp */ if(src->header.timestampEnabled) { rv |= writeJsonObjElm(ctx, UA_DECODEKEY_TIMESTAMP, &src->header.timestamp, @@ -93,7 +101,7 @@ UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, return UA_STATUSCODE_BADNOTSUPPORTED; /* Delta frames not supported */ if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { - /* KEYFRAME VARIANT */ + /* Variant */ for(UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { if(src->data.keyFrameData.fieldNames) rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]); @@ -105,7 +113,7 @@ UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, return rv; } } else if(src->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) { - /* KEYFRAME DATAVALUE */ + /* DataValue */ for(UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { if(src->data.keyFrameData.fieldNames) rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]); @@ -155,6 +163,8 @@ UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx if(rv != UA_STATUSCODE_GOOD) return rv; + /* TODO: Encode WriterGroupName */ + /* DataSetClassId */ if(src->dataSetClassIdEnabled) { rv |= writeJsonObjElm(ctx, UA_DECODEKEY_DATASETCLASSID, From 8ad619d47759e2c620d9f9289a04e4e3bbe2db84 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 20:16:53 +0100 Subject: [PATCH 103/158] feat(pubsub): Print the DataSetMessageType in the JSON encoding --- src/pubsub/ua_pubsub_networkmessage_json.c | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index adf8d0cae..ac5582c22 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -91,15 +91,19 @@ UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, return rv; } + /* MessageType */ + if(src->header.dataSetMessageType == UA_DATASETMESSAGE_DATAKEYFRAME) { + UA_String s = UA_STRING("ua-keyframe"); + rv |= writeJsonObjElm(ctx, UA_DECODEKEY_MESSAGETYPE, + &s, &UA_TYPES[UA_TYPES_STRING]); + } else { + /* TODO: Support other message types */ + return UA_STATUSCODE_BADNOTSUPPORTED; + } + rv |= writeJsonKey(ctx, UA_DECODEKEY_PAYLOAD); rv |= writeJsonObjStart(ctx); - /* TODO: currently no difference between delta and key frames. Own - * dataSetMessageType for json?. If the field names are not defined, write - * out empty field names. */ - if(src->header.dataSetMessageType != UA_DATASETMESSAGE_DATAKEYFRAME) - return UA_STATUSCODE_BADNOTSUPPORTED; /* Delta frames not supported */ - if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { /* Variant */ for(UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { @@ -384,18 +388,19 @@ static status DatasetMessage_Payload_decodeJsonInternal(ParseCtx *ctx, UA_DataSetMessage* dsm, const UA_DataType *type) { UA_ConfigurationVersionDataType cvd; - DecodeEntry entries[6] = { + DecodeEntry entries[7] = { {UA_DECODEKEY_DATASETWRITERID, &dsm->dataSetWriterId, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, {UA_DECODEKEY_SEQUENCENUMBER, &dsm->header.dataSetMessageSequenceNr, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, {UA_DECODEKEY_METADATAVERSION, &cvd, &MetaDataVersion_decodeJsonInternal, false, NULL}, {UA_DECODEKEY_TIMESTAMP, &dsm->header.timestamp, NULL, false, &UA_TYPES[UA_TYPES_DATETIME]}, {UA_DECODEKEY_DSM_STATUS, &dsm->header.status, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, + {UA_DECODEKEY_MESSAGETYPE, NULL, NULL, false, NULL}, {UA_DECODEKEY_PAYLOAD, dsm, &DataSetPayload_decodeJsonInternal, false, NULL} }; - status ret = decodeFields(ctx, entries, 6); + status ret = decodeFields(ctx, entries, 7); /* Error or no DatasetWriterId found or no payload found */ - if(ret != UA_STATUSCODE_GOOD || !entries[0].found || !entries[5].found) + if(ret != UA_STATUSCODE_GOOD || !entries[0].found || !entries[6].found) return UA_STATUSCODE_BADDECODINGERROR; dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; From 63d3317d3e7e40a43e19e4df138d889756d09d97 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 20:17:02 +0100 Subject: [PATCH 104/158] fix(pubsub): Print the PublisherId as String for the JSON encoding --- src/pubsub/ua_pubsub_networkmessage_json.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index ac5582c22..747d2a9cb 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -157,12 +157,17 @@ UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx rv |= writeJsonObjElm(ctx, UA_DECODEKEY_MESSAGETYPE, &s, &UA_TYPES[UA_TYPES_STRING]); - /* PublisherId */ + /* PublisherId, always encode as a JSON string */ if(src->publisherIdEnabled) { + UA_Byte buf[512]; + UA_ByteString bs = {512, buf}; UA_Variant v; UA_PublisherId_toVariant(&src->publisherId, &v); + rv |= UA_encodeJson(v.data, v.type, &bs, NULL); + if(rv != UA_STATUSCODE_GOOD) + return rv; rv |= writeJsonKey(ctx, UA_DECODEKEY_PUBLISHERID); - rv |= encodeJsonJumpTable[v.type->typeKind](ctx, v.data, v.type); + rv |= encodeJsonJumpTable[UA_DATATYPEKIND_STRING](ctx, &bs, NULL); } if(rv != UA_STATUSCODE_GOOD) return rv; From b895fa983391c1ec0bdac15ffbe8a548f6be9baa Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 22:14:38 +0100 Subject: [PATCH 105/158] refactor(pubsub): Always decode JSON payload as DataValue Variants and DataValue have the same layout now. --- src/pubsub/ua_pubsub_networkmessage_json.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index 747d2a9cb..c563dd6d1 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -360,27 +360,16 @@ DataSetPayload_decodeJsonInternal(ParseCtx *ctx, void* dsmP, const UA_DataType * /* Iterate over the key/value pairs in the object. Keys are stored in fieldnames. */ status ret = UA_STATUSCODE_GOOD; + dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; for(size_t i = 0; i < length; ++i) { UA_assert(currentTokenType(ctx) == CJ5_TOKEN_STRING); ret = decodeJsonJumpTable[UA_DATATYPEKIND_STRING](ctx, &fieldNames[i], type); if(ret != UA_STATUSCODE_GOOD) return ret; - /* TODO: Is field value a variant or datavalue? Current check if type and body present. */ - size_t searchResult = 0; - status foundType = lookAheadForKey(ctx, "Type", &searchResult); - status foundBody = lookAheadForKey(ctx, "Body", &searchResult); - if(foundType == UA_STATUSCODE_GOOD && foundBody == UA_STATUSCODE_GOOD) { - dsm->header.fieldEncoding = UA_FIELDENCODING_VARIANT; - ret = decodeJsonJumpTable[UA_DATATYPEKIND_VARIANT] - (ctx, &dsm->data.keyFrameData.dataSetFields[i].value, type); - dsm->data.keyFrameData.dataSetFields[i].hasValue = true; - } else { - dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; - ret = decodeJsonJumpTable[UA_DATATYPEKIND_DATAVALUE] - (ctx, &dsm->data.keyFrameData.dataSetFields[i], type); - dsm->data.keyFrameData.dataSetFields[i].hasValue = true; - } + /* TODO: Is field value a variant or datavalue? */ + ret = decodeJsonJumpTable[UA_DATATYPEKIND_DATAVALUE] + (ctx, &dsm->data.keyFrameData.dataSetFields[i], NULL); if(ret != UA_STATUSCODE_GOOD) return ret; From 422d18a397a9dd2a5bef095101ee4fdd9365932a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 22:15:10 +0100 Subject: [PATCH 106/158] feat(pubsub): It is allowed to have a single JSON object for the Messages field --- src/pubsub/ua_pubsub_networkmessage_json.c | 37 +++++++++++----------- tests/pubsub/check_pubsub_encoding_json.c | 16 +++++++++- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_json.c b/src/pubsub/ua_pubsub_networkmessage_json.c index c563dd6d1..fa032ffdd 100644 --- a/src/pubsub/ua_pubsub_networkmessage_json.c +++ b/src/pubsub/ua_pubsub_networkmessage_json.c @@ -416,29 +416,30 @@ DatasetMessage_Payload_decodeJsonInternal(ParseCtx *ctx, UA_DataSetMessage* dsm, static status DatasetMessage_Array_decodeJsonInternal(ParseCtx *ctx, void *UA_RESTRICT dst, const UA_DataType *type) { - /* Array! */ - if(currentTokenType(ctx) != CJ5_TOKEN_ARRAY) + /* Array or object */ + size_t length = 1; + if(currentTokenType(ctx) == CJ5_TOKEN_ARRAY) { + length = (size_t)ctx->tokens[ctx->index].size; + + /* Go to the first array member */ + ctx->index++; + + /* Return early for empty arrays */ + if(length == 0) + return UA_STATUSCODE_GOOD; + } else if(currentTokenType(ctx) != CJ5_TOKEN_OBJECT) { return UA_STATUSCODE_BADDECODINGERROR; - size_t length = (size_t)ctx->tokens[ctx->index].size; - - /* Return early for empty arrays */ - if(length == 0) - return UA_STATUSCODE_GOOD; - - UA_DataSetMessage *dsm = (UA_DataSetMessage*)dst; - - /* Go to the first array member */ - ctx->index++; + } /* Decode array members */ - status ret = UA_STATUSCODE_BADDECODINGERROR; + UA_DataSetMessage *dsm = (UA_DataSetMessage*)dst; for(size_t i = 0; i < length; ++i) { - ret = DatasetMessage_Payload_decodeJsonInternal(ctx, &dsm[i], NULL); + status ret = DatasetMessage_Payload_decodeJsonInternal(ctx, &dsm[i], NULL); if(ret != UA_STATUSCODE_GOOD) return ret; } - return ret; + return UA_STATUSCODE_GOOD; } static status @@ -473,9 +474,9 @@ NetworkMessage_decodeJsonInternal(ParseCtx *ctx, UA_NetworkMessage *dst) { if(found != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADNOTIMPLEMENTED; const cj5_token *bodyToken = &ctx->tokens[searchResultMessages]; - if(bodyToken->type != CJ5_TOKEN_ARRAY) - return UA_STATUSCODE_BADNOTIMPLEMENTED; - size_t messageCount = (size_t)ctx->tokens[searchResultMessages].size; + size_t messageCount = 1; + if(bodyToken->type == CJ5_TOKEN_ARRAY) + messageCount = (size_t)bodyToken->size; /* MessageType */ UA_Boolean isUaData = true; diff --git a/tests/pubsub/check_pubsub_encoding_json.c b/tests/pubsub/check_pubsub_encoding_json.c index 702dcd55e..2fc5f51e9 100644 --- a/tests/pubsub/check_pubsub_encoding_json.c +++ b/tests/pubsub/check_pubsub_encoding_json.c @@ -318,6 +318,20 @@ START_TEST(UA_NetworkMessage_json_decode) { } END_TEST +/* Messages are a single object and not an array */ +START_TEST(UA_NetworkMessage_json_decode_messageObject) { + // given + UA_NetworkMessage out; + memset(&out,0,sizeof(UA_NetworkMessage)); + UA_ByteString buf = UA_STRING("{\"MessageId\":\"5ED82C10-50BB-CD07-0120-22521081E8EE\",\"MessageType\":\"ua-data\",\"Messages\":{\"MetaDataVersion\":{\"MajorVersion\": 47, \"MinorVersion\": 47},\"DataSetWriterId\":62541,\"Status\":22,\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"UaType\":5,\"Value\":42},\"Server localtime\":{\"UaType\":1,\"Value\":true}}}}"); + // when + UA_StatusCode retval = UA_NetworkMessage_decodeJson(&buf, &out, NULL); + // then + ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); + UA_NetworkMessage_clear(&out); +} +END_TEST + START_TEST(UA_Networkmessage_DataSetFieldsNull_json_decode) { // given UA_NetworkMessage out; @@ -373,11 +387,11 @@ static Suite *testSuite_networkmessage(void) { Suite *s = suite_create("Built-in Data Types 62541-6 Json"); TCase *tc_json_networkmessage = tcase_create("networkmessage_json"); - tcase_add_test(tc_json_networkmessage, UA_PubSub_EncodeAllOptionalFields); tcase_add_test(tc_json_networkmessage, UA_PubSub_EnDecode); tcase_add_test(tc_json_networkmessage, UA_NetworkMessage_oneMessage_twoFields_json_decode); tcase_add_test(tc_json_networkmessage, UA_NetworkMessage_json_decode); + tcase_add_test(tc_json_networkmessage, UA_NetworkMessage_json_decode_messageObject); tcase_add_test(tc_json_networkmessage, UA_Networkmessage_DataSetFieldsNull_json_decode); tcase_add_test(tc_json_networkmessage, UA_NetworkMessage_fieldNames_json_decode); From 12a3e5c9158a44840783fb69e516bddf32b1c87b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Mon, 30 Dec 2024 22:15:30 +0100 Subject: [PATCH 107/158] refactor(pubsub): Adjust PubSub JSON unit test to the v1.05 format --- tests/pubsub/check_pubsub_encoding_json.c | 12 +++++------- tests/server/check_eventfilter_parser.c | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/pubsub/check_pubsub_encoding_json.c b/tests/pubsub/check_pubsub_encoding_json.c index 2fc5f51e9..ba412986c 100644 --- a/tests/pubsub/check_pubsub_encoding_json.c +++ b/tests/pubsub/check_pubsub_encoding_json.c @@ -79,7 +79,6 @@ START_TEST(UA_PubSub_EncodeAllOptionalFields) { m.payload.dataSetPayload.dataSetMessages[0].data.keyFrameData.dataSetFields[0].hasValue = true; size_t size = UA_NetworkMessage_calcSizeJsonInternal(&m, NULL, NULL, 0, true); - ck_assert_uint_eq(size, 340); UA_ByteString buffer; UA_StatusCode rv = UA_ByteString_allocBuffer(&buffer, size+1); @@ -89,13 +88,12 @@ START_TEST(UA_PubSub_EncodeAllOptionalFields) { memset(bufPos, 0, size+1); const UA_Byte *bufEnd = &(buffer.data[buffer.length]); - rv = UA_NetworkMessage_encodeJsonInternal(&m, &bufPos, &bufEnd, NULL, NULL, 0, true); *bufPos = 0; // then ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); - char* result = "{\"MessageId\":\"ABCDEFGH\",\"MessageType\":\"ua-data\",\"PublisherId\":65535,\"DataSetClassId\":\"00000001-0002-0003-0000-000000000000\",\"Messages\":[{\"DataSetWriterId\":12345,\"SequenceNumber\":4711,\"MetaDataVersion\":{\"MajorVersion\":42,\"MinorVersion\":7},\"Timestamp\":\"1601-01-13T20:38:31.1111111Z\",\"Status\":12345,\"Payload\":{\"Field1\":{\"Type\":7,\"Body\":27}}}]}"; + char* result = "{\"MessageId\":\"ABCDEFGH\",\"MessageType\":\"ua-data\",\"PublisherId\":\"65535\",\"DataSetClassId\":\"00000001-0002-0003-0000-000000000000\",\"Messages\":[{\"DataSetWriterId\":12345,\"SequenceNumber\":4711,\"MetaDataVersion\":{\"MajorVersion\":42,\"MinorVersion\":7},\"Timestamp\":\"1601-01-13T20:38:31.1111111Z\",\"Status\":12345,\"MessageType\":\"ua-keyframe\",\"Payload\":{\"Field1\":{\"UaType\":7,\"Value\":27}}}]}"; ck_assert_str_eq(result, (char*)buffer.data); UA_ByteString_clear(&buffer); @@ -209,7 +207,7 @@ END_TEST START_TEST(UA_NetworkMessage_oneMessage_twoFields_json_decode) { // given UA_NetworkMessage out; - UA_ByteString buf = UA_STRING("{\"MessageId\":\"5ED82C10-50BB-CD07-0120-22521081E8EE\",\"MessageType\":\"ua-data\",\"Messages\":[{\"DataSetWriterId\":62541,\"MetaDataVersion\":{\"MajorVersion\":1478393530,\"MinorVersion\":12345},\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"Type\":5,\"Body\":42},\"Server localtime\":{\"Type\":13,\"Body\":\"2018-06-05T05:58:36.000Z\"}}}]}"); + UA_ByteString buf = UA_STRING("{\"MessageId\":\"5ED82C10-50BB-CD07-0120-22521081E8EE\",\"MessageType\":\"ua-data\",\"Messages\":[{\"DataSetWriterId\":62541,\"MetaDataVersion\":{\"MajorVersion\":1478393530,\"MinorVersion\":12345},\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"UaType\":5,\"Value\":42},\"Server localtime\":{\"UaType\":13,\"Value\":\"2018-06-05T05:58:36.000Z\"}}}]}"); // when UA_StatusCode retval = UA_NetworkMessage_decodeJson(&buf, &out, NULL); // then @@ -270,7 +268,7 @@ START_TEST(UA_NetworkMessage_json_decode) { // given UA_NetworkMessage out; memset(&out,0,sizeof(UA_NetworkMessage)); - UA_ByteString buf = UA_STRING("{\"MessageId\":\"5ED82C10-50BB-CD07-0120-22521081E8EE\",\"MessageType\":\"ua-data\",\"Messages\":[{\"MetaDataVersion\":{\"MajorVersion\": 47, \"MinorVersion\": 47},\"DataSetWriterId\":62541,\"Status\":22,\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"Type\":5,\"Body\":42},\"Server localtime\":{\"Type\":1,\"Body\":true}}}]}"); + UA_ByteString buf = UA_STRING("{\"MessageId\":\"5ED82C10-50BB-CD07-0120-22521081E8EE\",\"MessageType\":\"ua-data\",\"Messages\":[{\"MetaDataVersion\":{\"MajorVersion\": 47, \"MinorVersion\": 47},\"DataSetWriterId\":62541,\"Status\":22,\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"UaType\":5,\"Value\":42},\"Server localtime\":{\"UaType\":1,\"Value\":true}}}]}"); // when UA_StatusCode retval = UA_NetworkMessage_decodeJson(&buf, &out, NULL); // then @@ -361,8 +359,8 @@ START_TEST(UA_NetworkMessage_fieldNames_json_decode) { "\"MessageType\":\"ua-data\",\"Messages\":" "[{\"DataSetWriterId\":62541,\"MetaDataVersion\":" "{\"MajorVersion\":1478393530,\"MinorVersion\":12345}," - "\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"Type\":5,\"Body\":42},\"Test2\":" - "{\"Type\":13,\"Body\":\"2018-06-05T05:58:36.000Z\"}}}]}"); + "\"SequenceNumber\":4711,\"Payload\":{\"Test\":{\"UaType\":5,\"Value\":42},\"Test2\":" + "{\"UaType\":13,\"Value\":\"2018-06-05T05:58:36.000Z\"}}}]}"); // when UA_StatusCode retval = UA_NetworkMessage_decodeJson(&buf, &out, NULL); // then diff --git a/tests/server/check_eventfilter_parser.c b/tests/server/check_eventfilter_parser.c index df8cf1c16..faebbb291 100644 --- a/tests/server/check_eventfilter_parser.c +++ b/tests/server/check_eventfilter_parser.c @@ -158,7 +158,7 @@ START_TEST(Case_11) { /* JSON */ START_TEST(Case_12) { char *inp = "SELECT /Severity " - "WHERE /Value == {\"Type\": 3,\"Body\": [1,2,1,5],\"Dimension\": [2,2]}"; + "WHERE /Value == {\"UaType\": 3,\"Value\": [1,2,1,5],\"Dimension\": [2,2]}"; UA_String case1 = UA_STRING(inp); UA_StatusCode res = UA_EventFilter_parse(&filter, case1, &options); ck_assert_int_eq(res, UA_STATUSCODE_GOOD); From 83ad60c1030f62f844205b44633baf2fa664db48 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 31 Dec 2024 13:04:54 +0100 Subject: [PATCH 108/158] refactor(core): Update RelativePath browsing for the new QualifiedName format --- src/util/ua_types_lex.c | 38 ++++++++++++----------------- src/util/ua_types_lex.re | 38 ++++++++++++----------------- tests/fuzz/fuzz_attributeoperand.cc | 8 ++++++ 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/src/util/ua_types_lex.c b/src/util/ua_types_lex.c index b6b0206d7..21c5ef367 100644 --- a/src/util/ua_types_lex.c +++ b/src/util/ua_types_lex.c @@ -693,30 +693,24 @@ relativepath_addelem(UA_RelativePath *rp, UA_RelativePathElement *el) { return UA_STATUSCODE_GOOD; } -/* Parse name string with '&' as the escape character */ +/* Parse name string with '&' as the escape character. Omit trailing &. Allow + * escaped characters in the middle. If the passed re2c lexing, then they are + * delimiters between escaped strings. */ static UA_StatusCode parse_qn_name(UA_String *name, const char *pos, - const char *end, Escaping esc) { - /* There must be no unescaped characters (also trailing &) */ - char *end_esc = find_unescaped((char *)(uintptr_t)pos, end, - (esc == ESCAPING_AND_EXTENDED)); - if(end_esc != end) - return UA_STATUSCODE_BADDECODINGERROR; - - size_t maxlen = (size_t)(end_esc - pos); - if(maxlen == 0) - return UA_STATUSCODE_GOOD; - + const char *end, Escaping esc) { /* Copy string */ - char *namestr = (char*)UA_malloc(maxlen); - if(!namestr) - return UA_STATUSCODE_BADOUTOFMEMORY; - memcpy(namestr, pos, maxlen); + UA_String tmp = {(size_t)(end - pos), (UA_Byte*)(uintptr_t)pos}; + UA_StatusCode res = UA_String_copy(&tmp, name); + if(res != UA_STATUSCODE_GOOD) + return res; /* Unescape in-situ */ - char *name_end = unescape(namestr, namestr + maxlen); - name->data = (UA_Byte*)namestr; - name->length = (size_t)(name_end - namestr); + char *esc_end = + unescape((char*)name->data, (const char*)name->data + name->length); + name->length = (size_t)(esc_end - (char*)name->data); + if(name->length == 0) + UA_String_clear(name); return UA_STATUSCODE_GOOD; } @@ -843,10 +837,8 @@ yy51: nsUri.data = (UA_Byte*)(uintptr_t)begin; if(nsMapping) res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, &qn->namespaceIndex); - if(res != UA_STATUSCODE_GOOD) { - UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; - return UA_String_copy(&total, &qn->name); - } + if(res != UA_STATUSCODE_GOOD) + return parse_qn_name(&qn->name, begin, end, esc); /* Use the full string for the name */ match_name: return parse_qn_name(&qn->name, pos, end, esc); diff --git a/src/util/ua_types_lex.re b/src/util/ua_types_lex.re index d28dc66f3..324f1ca38 100644 --- a/src/util/ua_types_lex.re +++ b/src/util/ua_types_lex.re @@ -303,30 +303,24 @@ relativepath_addelem(UA_RelativePath *rp, UA_RelativePathElement *el) { return UA_STATUSCODE_GOOD; } -/* Parse name string with '&' as the escape character */ +/* Parse name string with '&' as the escape character. Omit trailing &. Allow + * escaped characters in the middle. If the passed re2c lexing, then they are + * delimiters between escaped strings. */ static UA_StatusCode parse_qn_name(UA_String *name, const char *pos, - const char *end, Escaping esc) { - /* There must be no unescaped characters (also trailing &) */ - char *end_esc = find_unescaped((char *)(uintptr_t)pos, end, - (esc == ESCAPING_AND_EXTENDED)); - if(end_esc != end) - return UA_STATUSCODE_BADDECODINGERROR; - - size_t maxlen = (size_t)(end_esc - pos); - if(maxlen == 0) - return UA_STATUSCODE_GOOD; - + const char *end, Escaping esc) { /* Copy string */ - char *namestr = (char*)UA_malloc(maxlen); - if(!namestr) - return UA_STATUSCODE_BADOUTOFMEMORY; - memcpy(namestr, pos, maxlen); + UA_String tmp = {(size_t)(end - pos), (UA_Byte*)(uintptr_t)pos}; + UA_StatusCode res = UA_String_copy(&tmp, name); + if(res != UA_STATUSCODE_GOOD) + return res; /* Unescape in-situ */ - char *name_end = unescape(namestr, namestr + maxlen); - name->data = (UA_Byte*)namestr; - name->length = (size_t)(name_end - namestr); + char *esc_end = + unescape((char*)name->data, (const char*)name->data + name->length); + name->length = (size_t)(esc_end - (char*)name->data); + if(name->length == 0) + UA_String_clear(name); return UA_STATUSCODE_GOOD; } @@ -361,10 +355,8 @@ parse_qn(UA_QualifiedName *qn, const char *pos, const char *end, nsUri.data = (UA_Byte*)(uintptr_t)begin; if(nsMapping) res = UA_NamespaceMapping_uri2Index(nsMapping, nsUri, &qn->namespaceIndex); - if(res != UA_STATUSCODE_GOOD) { - UA_String total = {(size_t)(end - begin), (UA_Byte*)(uintptr_t)begin}; - return UA_String_copy(&total, &qn->name); - } + if(res != UA_STATUSCODE_GOOD) + return parse_qn_name(&qn->name, begin, end, esc); /* Use the full string for the name */ match_name: return parse_qn_name(&qn->name, pos, end, esc); diff --git a/tests/fuzz/fuzz_attributeoperand.cc b/tests/fuzz/fuzz_attributeoperand.cc index 03aed4f58..6cc9b3213 100644 --- a/tests/fuzz/fuzz_attributeoperand.cc +++ b/tests/fuzz/fuzz_attributeoperand.cc @@ -28,6 +28,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { const UA_String input = {size, (UA_Byte *) (void *) data}; UA_String out = UA_STRING_NULL; + UA_String out2 = UA_STRING_NULL; UA_AttributeOperand ao; UA_AttributeOperand ao2; @@ -46,10 +47,17 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { goto cleanup; UA_assert(ret == UA_STATUSCODE_GOOD); + ret = UA_AttributeOperand_print(&ao2, &out2); + if(ret == UA_STATUSCODE_BADOUTOFMEMORY) + goto cleanup; + UA_assert(ret == UA_STATUSCODE_GOOD); + + UA_assert(UA_String_equal(&out, &out2)); UA_assert(UA_equal(&ao, &ao2, &UA_TYPES[UA_TYPES_ATTRIBUTEOPERAND])); cleanup: UA_String_clear(&out); + UA_String_clear(&out2); UA_AttributeOperand_clear(&ao); UA_AttributeOperand_clear(&ao2); return 0; From 91392b48552a64cef6931a858fd6d67e4d8248bd Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 31 Dec 2024 13:10:05 +0100 Subject: [PATCH 109/158] refactor(core): Move ua_types_encoding_json.c -> ua_types_encoding_json_105.c to prepare having the old encoding in parallel --- CMakeLists.txt | 2 +- src/{ua_types_encoding_json.c => ua_types_encoding_json_105.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ua_types_encoding_json.c => ua_types_encoding_json_105.c} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d34ccf3d0..80c1fff13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -881,7 +881,7 @@ if(UA_ENABLE_JSON_ENCODING) ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.h) list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/deps/cj5.c ${PROJECT_SOURCE_DIR}/deps/parse_num.c - ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.c + ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json_105.c ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage_json.c) endif() diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json_105.c similarity index 100% rename from src/ua_types_encoding_json.c rename to src/ua_types_encoding_json_105.c From ced5913420d2684f1a7858a8c09becd20a14020b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 1 Jan 2025 13:18:00 +0100 Subject: [PATCH 110/158] feat(core): Re-add the legacy JSON encoding (pre 1.05) via the UA_ENABLE_JSON_ENCODING_LEGACY option --- CHANGES.md | 4 +- CMakeLists.txt | 7 + doc/building.rst | 5 +- include/open62541/config.h.in | 1 + src/ua_types_encoding_json.c | 2842 ++++++++++++++++++++++++++++++ src/ua_types_encoding_json_105.c | 11 +- 6 files changed, 2866 insertions(+), 4 deletions(-) create mode 100644 src/ua_types_encoding_json.c diff --git a/CHANGES.md b/CHANGES.md index 261e1ce58..1449e08b7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,9 @@ refactorings and bug fixes are not reported here. ### JSON encoding changed with the v1.05 specification The JSON encoding was reworked for the v1.05 version of the OPC UA -specification. The change breaks backwards compatibility. +specification. The change breaks backwards compatibility. The legacy JSON +encoding is still available throught the UA_ENABLE_JSON_ENCODING_LEGACY build +option. This legacy feature wil get removed at some point in the future. ### PubSub NetworkMessage structure has an explicit DataSetMessageSize diff --git a/CMakeLists.txt b/CMakeLists.txt index 80c1fff13..07375ebfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,12 @@ option(UA_ENABLE_NODESETLOADER "Enable nodesetLoader public API" OFF) option(UA_ENABLE_GDS_PUSHMANAGEMENT "Enable GDS pushManagement support" OFF) option(UA_ENABLE_DATATYPES_ALL "Generate all datatypes for namespace zero (uses more binary space)" ON) +option(UA_ENABLE_JSON_ENCODING_LEGACY "Use the old JSON encoding before the v1.05 spec" OFF) +mark_as_advanced(UA_ENABLE_JSON_ENCODING_LEGACY) +if(UA_ENABLE_JSON_ENCODING_LEGACY AND NOT UA_ENABLE_JSON_ENCODING) + message(FATAL_ERROR "UA_ENABLE_JSON_ENCODING_LEGACY requires UA_ENABLE_JSON_ENCODING also") +endif() + if(UA_INFORMATION_MODEL_AUTOLOAD AND NOT UA_BUILD_FUZZING) set(UA_ENABLE_NODESET_INJECTOR ON) endif() @@ -881,6 +887,7 @@ if(UA_ENABLE_JSON_ENCODING) ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.h) list(APPEND lib_sources ${PROJECT_SOURCE_DIR}/deps/cj5.c ${PROJECT_SOURCE_DIR}/deps/parse_num.c + ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json.c ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_json_105.c ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_networkmessage_json.c) endif() diff --git a/doc/building.rst b/doc/building.rst index 99c263d4d..f5631003c 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -336,7 +336,10 @@ Detailed SDK Features Enable diagnostics information exposed by the server. Enabled by default. **UA_ENABLE_JSON_ENCODING** - Enable JSON encoding. Enabled by default. + Enable JSON encoding. Enabled by default. The JSON encoding changed with the + 1.05 version of the OPC UA specification. The legacy encoding can be enabled + via the ``UA_ENABLE_JSON_ENCODING_LEGACY`` option. Note that this legacy + feature wil get removed at some point in the future. Some options are marked as advanced. The advanced options need to be toggled to be visible in the cmake GUIs. diff --git a/include/open62541/config.h.in b/include/open62541/config.h.in index 577a66ded..1a129127d 100644 --- a/include/open62541/config.h.in +++ b/include/open62541/config.h.in @@ -53,6 +53,7 @@ #cmakedefine UA_ENABLE_PARSING #cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS #cmakedefine UA_ENABLE_JSON_ENCODING +#cmakedefine UA_ENABLE_JSON_ENCODING_LEGACY #cmakedefine UA_ENABLE_XML_ENCODING #cmakedefine UA_ENABLE_MQTT #cmakedefine UA_ENABLE_NODESET_INJECTOR diff --git a/src/ua_types_encoding_json.c b/src/ua_types_encoding_json.c new file mode 100644 index 000000000..590d050c2 --- /dev/null +++ b/src/ua_types_encoding_json.c @@ -0,0 +1,2842 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) + * Copyright 2018 (c) Fraunhofer IOSB (Author: Lukas Meling) + */ + +/** + * This file contains the JSON encoding/decoding from before the v1.05 OPC UA + * specification. The changes in the v1.05 specification are breaking. The + * encoding is not compatible with new versions. Disable + * UA_ENABLE_JSON_ENCODING_LEGACY to use the new JSON encoding instead. + */ + +#include +#include + +#ifdef UA_ENABLE_JSON_ENCODING_LEGACY + +#include "ua_types_encoding_json.h" + +#include +#include + +#include "../deps/utf8.h" +#include "../deps/itoa.h" +#include "../deps/dtoa.h" +#include "../deps/parse_num.h" +#include "../deps/base64.h" +#include "../deps/libc_time.h" + +#ifndef UA_ENABLE_PARSING +#error UA_ENABLE_PARSING required for JSON encoding +#endif + +#ifndef UA_ENABLE_TYPEDESCRIPTION +#error UA_ENABLE_TYPEDESCRIPTION required for JSON encoding +#endif + +/* vs2008 does not have INFINITY and NAN defined */ +#ifndef INFINITY +# define INFINITY ((UA_Double)(DBL_MAX+DBL_MAX)) +#endif +#ifndef NAN +# define NAN ((UA_Double)(INFINITY-INFINITY)) +#endif + +#if defined(_MSC_VER) +# pragma warning(disable: 4756) +# pragma warning(disable: 4056) +#endif + +/* Have some slack at the end. E.g. for negative and very long years. */ +#define UA_JSON_DATETIME_LENGTH 40 + +/************/ +/* Encoding */ +/************/ + +#define ENCODE_JSON(TYPE) static status \ + TYPE##_encodeJson(CtxJson *ctx, const UA_##TYPE *src, const UA_DataType *type) + +#define ENCODE_DIRECT_JSON(SRC, TYPE) \ + TYPE##_encodeJson(ctx, (const UA_##TYPE*)SRC, NULL) + +static status UA_FUNC_ATTR_WARN_UNUSED_RESULT +writeChar(CtxJson *ctx, char c) { + if(ctx->pos >= ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + if(!ctx->calcOnly) + *ctx->pos = (UA_Byte)c; + ctx->pos++; + return UA_STATUSCODE_GOOD; +} + +static status UA_FUNC_ATTR_WARN_UNUSED_RESULT +writeChars(CtxJson *ctx, const char *c, size_t len) { + if(ctx->pos + len > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + if(!ctx->calcOnly) + memcpy(ctx->pos, c, len); + ctx->pos += len; + return UA_STATUSCODE_GOOD; +} + +#define WRITE_JSON_ELEMENT(ELEM) \ + UA_FUNC_ATTR_WARN_UNUSED_RESULT status \ + writeJson##ELEM(CtxJson *ctx) + +static WRITE_JSON_ELEMENT(Quote) { + return writeChar(ctx, '\"'); +} + +UA_StatusCode +writeJsonBeforeElement(CtxJson *ctx, UA_Boolean distinct) { + UA_StatusCode res = UA_STATUSCODE_GOOD; + /* Comma if needed */ + if(ctx->commaNeeded[ctx->depth]) + res |= writeChar(ctx, ','); + if(ctx->prettyPrint) { + if(distinct) { + /* Newline and indent if needed */ + res |= writeChar(ctx, '\n'); + for(size_t i = 0; i < ctx->depth; i++) + res |= writeChar(ctx, '\t'); + } else if(ctx->commaNeeded[ctx->depth]) { + /* Space after the comma if no newline */ + res |= writeChar(ctx, ' '); + } + } + return res; +} + +WRITE_JSON_ELEMENT(ObjStart) { + /* increase depth, save: before first key-value no comma needed. */ + if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION - 1) + return UA_STATUSCODE_BADENCODINGERROR; + ctx->depth++; + ctx->commaNeeded[ctx->depth] = false; + return writeChar(ctx, '{'); +} + +WRITE_JSON_ELEMENT(ObjEnd) { + if(ctx->depth == 0) + return UA_STATUSCODE_BADENCODINGERROR; + + UA_Boolean have_elem = ctx->commaNeeded[ctx->depth]; + ctx->depth--; + ctx->commaNeeded[ctx->depth] = true; + + UA_StatusCode res = UA_STATUSCODE_GOOD; + if(ctx->prettyPrint && have_elem) { + res |= writeChar(ctx, '\n'); + for(size_t i = 0; i < ctx->depth; i++) + res |= writeChar(ctx, '\t'); + } + return res | writeChar(ctx, '}'); +} + +WRITE_JSON_ELEMENT(ArrStart) { + /* increase depth, save: before first array entry no comma needed. */ + if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION - 1) + return UA_STATUSCODE_BADENCODINGERROR; + ctx->depth++; + ctx->commaNeeded[ctx->depth] = false; + return writeChar(ctx, '['); +} + +status +writeJsonArrEnd(CtxJson *ctx, const UA_DataType *type) { + if(ctx->depth == 0) + return UA_STATUSCODE_BADENCODINGERROR; + UA_Boolean have_elem = ctx->commaNeeded[ctx->depth]; + ctx->depth--; + ctx->commaNeeded[ctx->depth] = true; + + /* If the array does not contain JSON objects (with a newline after), then + * add the closing ] on the same line */ + UA_Boolean distinct = (!type || type->typeKind > UA_DATATYPEKIND_DOUBLE); + UA_StatusCode res = UA_STATUSCODE_GOOD; + if(ctx->prettyPrint && have_elem && distinct) { + res |= writeChar(ctx, '\n'); + for(size_t i = 0; i < ctx->depth; i++) + res |= writeChar(ctx, '\t'); + } + return res | writeChar(ctx, ']'); +} + +status +writeJsonArrElm(CtxJson *ctx, const void *value, + const UA_DataType *type) { + UA_Boolean distinct = (type->typeKind > UA_DATATYPEKIND_DOUBLE); + status ret = writeJsonBeforeElement(ctx, distinct); + ctx->commaNeeded[ctx->depth] = true; + return ret | encodeJsonJumpTable[type->typeKind](ctx, value, type); +} + +status +writeJsonObjElm(CtxJson *ctx, const char *key, + const void *value, const UA_DataType *type) { + return writeJsonKey(ctx, key) | encodeJsonJumpTable[type->typeKind](ctx, value, type); +} + +/* Keys for JSON */ + +/* LocalizedText */ +static const char* UA_JSONKEY_LOCALE = "Locale"; +static const char* UA_JSONKEY_TEXT = "Text"; + +/* QualifiedName */ +static const char* UA_JSONKEY_NAME = "Name"; +static const char* UA_JSONKEY_URI = "Uri"; + +/* NodeId */ +static const char* UA_JSONKEY_ID = "Id"; +static const char* UA_JSONKEY_IDTYPE = "IdType"; +static const char* UA_JSONKEY_NAMESPACE = "Namespace"; + +/* ExpandedNodeId */ +static const char* UA_JSONKEY_SERVERURI = "ServerUri"; + +/* Variant */ +static const char* UA_JSONKEY_TYPE = "Type"; +static const char* UA_JSONKEY_BODY = "Body"; +static const char* UA_JSONKEY_DIMENSION = "Dimension"; + +/* DataValue */ +static const char* UA_JSONKEY_VALUE = "Value"; +static const char* UA_JSONKEY_STATUS = "Status"; +static const char* UA_JSONKEY_SOURCETIMESTAMP = "SourceTimestamp"; +static const char* UA_JSONKEY_SOURCEPICOSECONDS = "SourcePicoseconds"; +static const char* UA_JSONKEY_SERVERTIMESTAMP = "ServerTimestamp"; +static const char* UA_JSONKEY_SERVERPICOSECONDS = "ServerPicoseconds"; + +/* ExtensionObject */ +static const char* UA_JSONKEY_ENCODING = "Encoding"; +static const char* UA_JSONKEY_TYPEID = "TypeId"; + +/* StatusCode */ +static const char* UA_JSONKEY_CODE = "Code"; +static const char* UA_JSONKEY_SYMBOL = "Symbol"; + +/* DiagnosticInfo */ +static const char* UA_JSONKEY_SYMBOLICID = "SymbolicId"; +static const char* UA_JSONKEY_NAMESPACEURI = "NamespaceUri"; +static const char* UA_JSONKEY_LOCALIZEDTEXT = "LocalizedText"; +static const char* UA_JSONKEY_ADDITIONALINFO = "AdditionalInfo"; +static const char* UA_JSONKEY_INNERSTATUSCODE = "InnerStatusCode"; +static const char* UA_JSONKEY_INNERDIAGNOSTICINFO = "InnerDiagnosticInfo"; + +/* Writes null terminated string to output buffer (current ctx->pos). Writes + * comma in front of key if needed. Encapsulates key in quotes. */ +status UA_FUNC_ATTR_WARN_UNUSED_RESULT +writeJsonKey(CtxJson *ctx, const char* key) { + status ret = writeJsonBeforeElement(ctx, true); + ctx->commaNeeded[ctx->depth] = true; + if(!ctx->unquotedKeys) + ret |= writeChar(ctx, '\"'); + ret |= writeChars(ctx, key, strlen(key)); + if(!ctx->unquotedKeys) + ret |= writeChar(ctx, '\"'); + ret |= writeChar(ctx, ':'); + if(ctx->prettyPrint) + ret |= writeChar(ctx, ' '); + return ret; +} + +static bool +isNull(const void *p, const UA_DataType *type) { + if(UA_DataType_isNumeric(type) || type->typeKind == UA_DATATYPEKIND_BOOLEAN) + return false; + UA_STACKARRAY(char, buf, type->memSize); + memset(buf, 0, type->memSize); + return UA_equal(buf, p, type); +} + +/* Boolean */ +ENCODE_JSON(Boolean) { + if(*src == true) + return writeChars(ctx, "true", 4); + return writeChars(ctx, "false", 5); +} + +/* Byte */ +ENCODE_JSON(Byte) { + char buf[4]; + UA_UInt16 digits = itoaUnsigned(*src, buf, 10); + + /* Ensure destination can hold the data- */ + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + /* Copy digits to the output string/buffer. */ + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* signed Byte */ +ENCODE_JSON(SByte) { + char buf[5]; + UA_UInt16 digits = itoaSigned(*src, buf); + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* UInt16 */ +ENCODE_JSON(UInt16) { + char buf[6]; + UA_UInt16 digits = itoaUnsigned(*src, buf, 10); + + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* Int16 */ +ENCODE_JSON(Int16) { + char buf[7]; + UA_UInt16 digits = itoaSigned(*src, buf); + + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* UInt32 */ +ENCODE_JSON(UInt32) { + char buf[11]; + UA_UInt16 digits = itoaUnsigned(*src, buf, 10); + + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* Int32 */ +ENCODE_JSON(Int32) { + char buf[12]; + UA_UInt16 digits = itoaSigned(*src, buf); + + if(ctx->pos + digits > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, digits); + ctx->pos += digits; + return UA_STATUSCODE_GOOD; +} + +/* UInt64 */ +ENCODE_JSON(UInt64) { + char buf[23]; + buf[0] = '\"'; + UA_UInt16 digits = itoaUnsigned(*src, buf + 1, 10); + buf[digits + 1] = '\"'; + UA_UInt16 length = (UA_UInt16)(digits + 2); + + if(ctx->pos + length > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, length); + ctx->pos += length; + return UA_STATUSCODE_GOOD; +} + +/* Int64 */ +ENCODE_JSON(Int64) { + char buf[23]; + buf[0] = '\"'; + UA_UInt16 digits = itoaSigned(*src, buf + 1); + buf[digits + 1] = '\"'; + UA_UInt16 length = (UA_UInt16)(digits + 2); + + if(ctx->pos + length > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buf, length); + ctx->pos += length; + return UA_STATUSCODE_GOOD; +} + +ENCODE_JSON(Float) { + char buffer[32]; + size_t len; + if(*src != *src) { + strcpy(buffer, "\"NaN\""); + len = strlen(buffer); + } else if(*src == INFINITY) { + strcpy(buffer, "\"Infinity\""); + len = strlen(buffer); + } else if(*src == -INFINITY) { + strcpy(buffer, "\"-Infinity\""); + len = strlen(buffer); + } else { + len = dtoa((UA_Double)*src, buffer); + } + + if(ctx->pos + len > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buffer, len); + ctx->pos += len; + return UA_STATUSCODE_GOOD; +} + +ENCODE_JSON(Double) { + char buffer[32]; + size_t len; + if(*src != *src) { + strcpy(buffer, "\"NaN\""); + len = strlen(buffer); + } else if(*src == INFINITY) { + strcpy(buffer, "\"Infinity\""); + len = strlen(buffer); + } else if(*src == -INFINITY) { + strcpy(buffer, "\"-Infinity\""); + len = strlen(buffer); + } else { + len = dtoa(*src, buffer); + } + + if(ctx->pos + len > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + if(!ctx->calcOnly) + memcpy(ctx->pos, buffer, len); + ctx->pos += len; + return UA_STATUSCODE_GOOD; +} + +static status +encodeJsonArray(CtxJson *ctx, const void *ptr, size_t length, + const UA_DataType *type) { + /* Null-arrays (length -1) are written as empty arrays '[]'. + * TODO: Clarify the difference between length -1 and length 0 in JSON. */ + status ret = writeJsonArrStart(ctx); + if(!ptr) + return ret | writeJsonArrEnd(ctx, type); + + uintptr_t uptr = (uintptr_t)ptr; + encodeJsonSignature encodeType = encodeJsonJumpTable[type->typeKind]; + UA_Boolean distinct = (type->typeKind > UA_DATATYPEKIND_DOUBLE); + for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) { + ret |= writeJsonBeforeElement(ctx, distinct); + if(isNull((const void*)uptr, type)) + ret |= writeChars(ctx, "null", 4); + else + ret |= encodeType(ctx, (const void*)uptr, type); + ctx->commaNeeded[ctx->depth] = true; + uptr += type->memSize; + } + return ret | writeJsonArrEnd(ctx, type); +} + +static const u8 hexmap[16] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +ENCODE_JSON(String) { + if(!src->data) + return writeChars(ctx, "null", 4); + + if(src->length == 0) + return writeJsonQuote(ctx) | writeJsonQuote(ctx); + + UA_StatusCode ret = writeJsonQuote(ctx); + + const unsigned char *end = src->data + src->length; + for(const unsigned char *pos = src->data; pos < end; pos++) { + /* Skip to the first character that needs escaping */ + const unsigned char *start = pos; + for(; pos < end; pos++) { + if(*pos < ' ' || *pos == 127 || *pos == '\\' || *pos == '\"') + break; + } + + /* Write out the unescaped sequence */ + if(ctx->pos + (pos - start) > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + if(!ctx->calcOnly) + memcpy(ctx->pos, start, (size_t)(pos - start)); + ctx->pos += pos - start; + + /* The unescaped sequence reached the end */ + if(pos == end) + break; + + /* Write an escaped character */ + char *escape_text; + char escape_buf[6]; + size_t escape_len = 2; + switch(*pos) { + case '\b': escape_text = "\\b"; break; + case '\f': escape_text = "\\f"; break; + case '\n': escape_text = "\\n"; break; + case '\r': escape_text = "\\r"; break; + case '\t': escape_text = "\\t"; break; + default: + escape_text = escape_buf; + if(*pos >= ' ' && *pos != 127) { + /* Escape \ or " */ + escape_buf[0] = '\\'; + escape_buf[1] = *pos; + } else { + /* Unprintable characters need to be escaped */ + escape_buf[0] = '\\'; + escape_buf[1] = 'u'; + escape_buf[2] = '0'; + escape_buf[3] = '0'; + escape_buf[4] = hexmap[*pos >> 4]; + escape_buf[5] = hexmap[*pos & 0x0f]; + escape_len = 6; + } + break; + } + + /* Enough space? */ + if(ctx->pos + escape_len > ctx->end) + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + + /* Write the escaped character */ + if(!ctx->calcOnly) + memcpy(ctx->pos, escape_text, escape_len); + ctx->pos += escape_len; + } + + return ret | writeJsonQuote(ctx); +} + +ENCODE_JSON(ByteString) { + if(!src->data) + return writeChars(ctx, "null", 4); + + if(src->length == 0) { + status retval = writeJsonQuote(ctx); + retval |= writeJsonQuote(ctx); + return retval; + } + + status ret = writeJsonQuote(ctx); + size_t flen = 0; + unsigned char *ba64 = UA_base64(src->data, src->length, &flen); + + /* Not converted, no mem */ + if(!ba64) + return UA_STATUSCODE_BADENCODINGERROR; + + if(ctx->pos + flen > ctx->end) { + UA_free(ba64); + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + } + + /* Copy flen bytes to output stream. */ + if(!ctx->calcOnly) + memcpy(ctx->pos, ba64, flen); + ctx->pos += flen; + + /* Base64 result no longer needed */ + UA_free(ba64); + + return ret | writeJsonQuote(ctx); +} + +/* Guid */ +ENCODE_JSON(Guid) { + if(ctx->pos + 38 > ctx->end) /* 36 + 2 (") */ + return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; + status ret = writeJsonQuote(ctx); + if(!ctx->calcOnly) + UA_Guid_to_hex(src, ctx->pos, false); + ctx->pos += 36; + return ret | writeJsonQuote(ctx); +} + +static u8 +printNumber(i32 n, char *pos, u8 min_digits) { + char digits[10]; + u8 len = 0; + /* Handle negative values */ + if(n < 0) { + pos[len++] = '-'; + n = -n; + } + + /* Extract the digits */ + u8 i = 0; + for(; i < min_digits || n > 0; i++) { + digits[i] = (char)((n % 10) + '0'); + n /= 10; + } + + /* Print in reverse order and return */ + for(; i > 0; i--) + pos[len++] = digits[i-1]; + return len; +} + +ENCODE_JSON(DateTime) { + UA_DateTimeStruct tSt = UA_DateTime_toStruct(*src); + + /* Format: -yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z' is used. max 31 bytes. + * Note the optional minus for negative years. */ + char buffer[UA_JSON_DATETIME_LENGTH]; + char *pos = buffer; + pos += printNumber(tSt.year, pos, 4); + *(pos++) = '-'; + pos += printNumber(tSt.month, pos, 2); + *(pos++) = '-'; + pos += printNumber(tSt.day, pos, 2); + *(pos++) = 'T'; + pos += printNumber(tSt.hour, pos, 2); + *(pos++) = ':'; + pos += printNumber(tSt.min, pos, 2); + *(pos++) = ':'; + pos += printNumber(tSt.sec, pos, 2); + *(pos++) = '.'; + pos += printNumber(tSt.milliSec, pos, 3); + pos += printNumber(tSt.microSec, pos, 3); + pos += printNumber(tSt.nanoSec, pos, 3); + + UA_assert(pos <= &buffer[UA_JSON_DATETIME_LENGTH]); + + /* Remove trailing zeros */ + pos--; + while(*pos == '0') + pos--; + if(*pos == '.') + pos--; + + *(++pos) = 'Z'; + UA_String str = {((uintptr_t)pos - (uintptr_t)buffer)+1, (UA_Byte*)buffer}; + return ENCODE_DIRECT_JSON(&str, String); +} + +/* NodeId */ +static status +NodeId_encodeJsonInternal(CtxJson *ctx, UA_NodeId const *src) { + status ret = UA_STATUSCODE_GOOD; + switch(src->identifierType) { + case UA_NODEIDTYPE_NUMERIC: + ret |= writeJsonKey(ctx, UA_JSONKEY_ID); + ret |= ENCODE_DIRECT_JSON(&src->identifier.numeric, UInt32); + break; + case UA_NODEIDTYPE_STRING: + ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); + ret |= writeChar(ctx, '1'); + ret |= writeJsonKey(ctx, UA_JSONKEY_ID); + ret |= ENCODE_DIRECT_JSON(&src->identifier.string, String); + break; + case UA_NODEIDTYPE_GUID: + ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); + ret |= writeChar(ctx, '2'); + ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ + ret |= ENCODE_DIRECT_JSON(&src->identifier.guid, Guid); + break; + case UA_NODEIDTYPE_BYTESTRING: + ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); + ret |= writeChar(ctx, '3'); + ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ + ret |= ENCODE_DIRECT_JSON(&src->identifier.byteString, ByteString); + break; + default: + return UA_STATUSCODE_BADINTERNALERROR; + } + return ret; +} + +ENCODE_JSON(NodeId) { + /* Encode as string (non-standard). Encode with the standard utf8 escaping. + * As the NodeId can contain quote characters, etc. */ + UA_StatusCode ret = UA_STATUSCODE_GOOD; + if(ctx->stringNodeIds) { + UA_String out = UA_STRING_NULL; + ret |= UA_NodeId_print(src, &out); + ret |= ENCODE_DIRECT_JSON(&out, String); + UA_String_clear(&out); + return ret; + } + + /* Encode as object */ + ret |= writeJsonObjStart(ctx); + ret |= NodeId_encodeJsonInternal(ctx, src); + if(ctx->useReversible) { + if(src->namespaceIndex > 0) { + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); + ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + } + } else { + /* For the non-reversible encoding, the field is the NamespaceUri + * associated with the NamespaceIndex, encoded as a JSON string. + * A NamespaceIndex of 1 is always encoded as a JSON number. */ + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); + if(src->namespaceIndex == 1) { + ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + } else { + /* Check if Namespace given and in range */ + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); + } else { + /* If not found, print the identifier */ + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); + } + } + } + + return ret | writeJsonObjEnd(ctx); +} + +/* ExpandedNodeId */ +ENCODE_JSON(ExpandedNodeId) { + /* Encode as string (non-standard). Encode with utf8 escaping as the NodeId + * can contain quote characters, etc. */ + UA_StatusCode ret = UA_STATUSCODE_GOOD; + if(ctx->stringNodeIds) { + UA_String out = UA_STRING_NULL; + ret |= UA_ExpandedNodeId_print(src, &out); + ret |= ENCODE_DIRECT_JSON(&out, String); + UA_String_clear(&out); + return ret; + } + + /* Encode as object */ + ret |= writeJsonObjStart(ctx); + + /* Encode the identifier portion */ + ret |= NodeId_encodeJsonInternal(ctx, &src->nodeId); + + if(ctx->useReversible) { + /* Reversible Case */ + + if(src->namespaceUri.data) { + /* If the NamespaceUri is specified it is encoded as a JSON string + * in this field */ + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); + ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); + } else if(src->nodeId.namespaceIndex > 0) { + /* If the NamespaceUri is not specified, the NamespaceIndex is + * encoded. Encoded as a JSON number for the reversible encoding. + * Omitted if the NamespaceIndex equals 0. */ + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); + ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); + } + + /* Encode the serverIndex/Url. As a JSON number for the reversible + * encoding. Omitted if the ServerIndex equals 0. */ + if(src->serverIndex > 0) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); + ret |= ENCODE_DIRECT_JSON(&src->serverIndex, UInt32); + } + } else { + /* Non-Reversible Case */ + + /* If the NamespaceUri is not specified, the NamespaceIndex is encoded + * with these rules: For the non-reversible encoding the field is the + * NamespaceUri associated with the NamespaceIndex encoded as a JSON + * string. A NamespaceIndex of 1 is always encoded as a JSON number. */ + + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); + if(src->namespaceUri.data) { + ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); + } else { + if(src->nodeId.namespaceIndex == 1) { + ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); + } else { + /* Check if Namespace given and in range */ + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->nodeId.namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); + } else { + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); + } + } + } + + /* For the non-reversible encoding, this field is the ServerUri + * associated with the ServerIndex portion of the ExpandedNodeId, + * encoded as a JSON string. */ + + /* Check if server given and in range */ + if(src->serverIndex >= ctx->serverUrisSize || !ctx->serverUris) + return UA_STATUSCODE_BADNOTFOUND; + + UA_String serverUriEntry = ctx->serverUris[src->serverIndex]; + ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); + ret |= ENCODE_DIRECT_JSON(&serverUriEntry, String); + } + + return ret | writeJsonObjEnd(ctx); +} + +/* LocalizedText */ +ENCODE_JSON(LocalizedText) { + if(ctx->useReversible) { + status ret = writeJsonObjStart(ctx); + ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); + ret |= ENCODE_DIRECT_JSON(&src->locale, String); + ret |= writeJsonKey(ctx, UA_JSONKEY_TEXT); + ret |= ENCODE_DIRECT_JSON(&src->text, String); + return ret | writeJsonObjEnd(ctx); + } + + /* For the non-reversible form, LocalizedText value shall be encoded as a + * JSON string containing the Text component.*/ + return ENCODE_DIRECT_JSON(&src->text, String); +} + +ENCODE_JSON(QualifiedName) { + status ret = writeJsonObjStart(ctx); + ret |= writeJsonKey(ctx, UA_JSONKEY_NAME); + ret |= ENCODE_DIRECT_JSON(&src->name, String); + + if(ctx->useReversible) { + if(src->namespaceIndex != 0) { + ret |= writeJsonKey(ctx, UA_JSONKEY_URI); + ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + } + } else { + /* For the non-reversible form, the NamespaceUri associated with the + * NamespaceIndex portion of the QualifiedName is encoded as JSON string + * unless the NamespaceIndex is 1 or if NamespaceUri is unknown. In + * these cases, the NamespaceIndex is encoded as a JSON number. */ + ret |= writeJsonKey(ctx, UA_JSONKEY_URI); + if(src->namespaceIndex == 1) { + ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); + } else { + /* Check if Namespace given and in range */ + UA_String nsUri = UA_STRING_NULL; + UA_UInt16 ns = src->namespaceIndex; + if(ctx->namespaceMapping) + UA_NamespaceMapping_index2Uri(ctx->namespaceMapping, ns, &nsUri); + if(nsUri.length > 0) { + ret |= ENCODE_DIRECT_JSON(&nsUri, String); + } else { + ret |= ENCODE_DIRECT_JSON(&ns, UInt16); /* If not encode as number */ + } + } + } + + return ret | writeJsonObjEnd(ctx); +} + +ENCODE_JSON(StatusCode) { + if(ctx->useReversible) + return ENCODE_DIRECT_JSON(src, UInt32); + + const char *codename = UA_StatusCode_name(*src); + UA_String statusDescription = UA_STRING((char*)(uintptr_t)codename); + + status ret = UA_STATUSCODE_GOOD; + ret |= writeJsonObjStart(ctx); + ret |= writeJsonKey(ctx, UA_JSONKEY_CODE); + ret |= ENCODE_DIRECT_JSON(src, UInt32); + ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOL); + ret |= ENCODE_DIRECT_JSON(&statusDescription, String); + ret |= writeJsonObjEnd(ctx); + return ret; +} + +/* ExtensionObject */ +ENCODE_JSON(ExtensionObject) { + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) + return writeChars(ctx, "null", 4); + + /* Must have a type set if data is decoded */ + if(src->encoding != UA_EXTENSIONOBJECT_ENCODED_BYTESTRING && + src->encoding != UA_EXTENSIONOBJECT_ENCODED_XML && + !src->content.decoded.type) + return UA_STATUSCODE_BADENCODINGERROR; + + status ret = writeJsonObjStart(ctx); + + /* Reversible encoding */ + if(ctx->useReversible) { + /* Write the type NodeId */ + ret |= writeJsonKey(ctx, UA_JSONKEY_TYPEID); + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || + src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) + ret |= ENCODE_DIRECT_JSON(&src->content.encoded.typeId, NodeId); + else + ret |= ENCODE_DIRECT_JSON(&src->content.decoded.type->typeId, NodeId); + + /* Write the encoding */ + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) { + ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); + ret |= writeChar(ctx, '1'); + } else if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) { + ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); + ret |= writeChar(ctx, '2'); + } + } + + /* Write the body */ + ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); + if(src->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING || + src->encoding == UA_EXTENSIONOBJECT_ENCODED_XML) { + ret |= ENCODE_DIRECT_JSON(&src->content.encoded.body, String); + } else { + const UA_DataType *t = src->content.decoded.type; + ret |= encodeJsonJumpTable[t->typeKind] + (ctx, src->content.decoded.data, t); + } + + return ret | writeJsonObjEnd(ctx); +} + +/* Non-builtin types get wrapped in an ExtensionObject */ +static status +encodeScalarJsonWrapExtensionObject(CtxJson *ctx, const UA_Variant *src) { + const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); + const void *ptr = src->data; + const UA_DataType *type = src->type; + + /* Set up a temporary ExtensionObject to wrap the data */ + UA_ExtensionObject eo; + if(!isBuiltin) { + UA_ExtensionObject_init(&eo); + eo.encoding = UA_EXTENSIONOBJECT_DECODED; + eo.content.decoded.type = src->type; + eo.content.decoded.data = src->data; + ptr = &eo; + type = &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]; + } + + return encodeJsonJumpTable[type->typeKind](ctx, ptr, type); +} + +/* Non-builtin types get wrapped in an ExtensionObject */ +static status +encodeArrayJsonWrapExtensionObject(CtxJson *ctx, const void *data, + size_t size, const UA_DataType *type) { + if(size > UA_INT32_MAX) + return UA_STATUSCODE_BADENCODINGERROR; + + status ret = writeJsonArrStart(ctx); + + u16 memSize = type->memSize; + const UA_Boolean isBuiltin = (type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); + if(isBuiltin) { + uintptr_t ptr = (uintptr_t)data; + for(size_t i = 0; i < size && ret == UA_STATUSCODE_GOOD; ++i) { + ret |= writeJsonArrElm(ctx, (const void*)ptr, type); + ptr += memSize; + } + } else { + /* Set up a temporary ExtensionObject to wrap the data */ + UA_ExtensionObject eo; + UA_ExtensionObject_init(&eo); + eo.encoding = UA_EXTENSIONOBJECT_DECODED; + eo.content.decoded.type = type; + eo.content.decoded.data = (void*)(uintptr_t)data; + for(size_t i = 0; i < size && ret == UA_STATUSCODE_GOOD; ++i) { + ret |= writeJsonArrElm(ctx, &eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); + eo.content.decoded.data = (void*) + ((uintptr_t)eo.content.decoded.data + memSize); + } + } + + return ret | writeJsonArrEnd(ctx, type); +} + +static status +addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type, + size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex, + size_t dimensionSize) { + /* Stop recursion: The inner arrays are written */ + status ret; + if(dimensionIndex == (dimensionSize - 1)) { + u8 *ptr = ((u8 *)array) + (type->memSize * *index); + u32 size = arrayDimensions[dimensionIndex]; + (*index) += arrayDimensions[dimensionIndex]; + return encodeArrayJsonWrapExtensionObject(ctx, ptr, size, type); + } + + /* Recurse to the next dimension */ + ret = writeJsonArrStart(ctx); + for(size_t i = 0; i < arrayDimensions[dimensionIndex]; i++) { + ret |= writeJsonBeforeElement(ctx, true); + ret |= addMultiArrayContentJSON(ctx, array, type, index, arrayDimensions, + dimensionIndex + 1, dimensionSize); + ctx->commaNeeded[ctx->depth] = true; + } + return ret | writeJsonArrEnd(ctx, type); +} + +ENCODE_JSON(Variant) { + /* If type is 0 (NULL) the Variant contains a NULL value and the containing + * JSON object shall be omitted or replaced by the JSON literal ‘null’ (when + * an element of a JSON array). */ + if(!src->type) + return writeJsonObjStart(ctx) | writeJsonObjEnd(ctx); + + /* Set the content type in the encoding mask */ + const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); + + /* Set the array type in the encoding mask */ + const bool isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL; + const bool hasDimensions = isArray && src->arrayDimensionsSize > 0; + + /* We cannot directly encode a variant inside a variant (but arrays of + * variant are possible) */ + UA_Boolean wrapEO = !isBuiltin; + if(src->type == &UA_TYPES[UA_TYPES_VARIANT] && !isArray) + wrapEO = true; + if(ctx->prettyPrint) + wrapEO = false; /* Don't wrap values in ExtensionObjects for pretty-printing */ + + status ret = writeJsonObjStart(ctx); + + /* Write the type NodeId */ + if(ctx->useReversible) { + ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); + if(ctx->prettyPrint) { + ret |= writeChars(ctx, src->type->typeName, strlen(src->type->typeName)); + } else { + /* Write the NodeId for the reversible form */ + UA_UInt32 typeId = src->type->typeId.identifier.numeric; + if(wrapEO) + typeId = UA_TYPES[UA_TYPES_EXTENSIONOBJECT].typeId.identifier.numeric; + ret |= ENCODE_DIRECT_JSON(&typeId, UInt32); + } + } + + /* Write the Variant body */ + ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); + + if(!isArray) { + ret |= encodeScalarJsonWrapExtensionObject(ctx, src); + } else { + if(ctx->useReversible || !hasDimensions) { + ret |= encodeArrayJsonWrapExtensionObject(ctx, src->data, + src->arrayLength, src->type); + if(hasDimensions) { + ret |= writeJsonKey(ctx, UA_JSONKEY_DIMENSION); + ret |= encodeJsonArray(ctx, src->arrayDimensions, src->arrayDimensionsSize, + &UA_TYPES[UA_TYPES_INT32]); + } + } else { + /* Special case of non-reversible array with dimensions */ + size_t index = 0; + ret |= addMultiArrayContentJSON(ctx, src->data, src->type, &index, + src->arrayDimensions, 0, + src->arrayDimensionsSize); + } + } + + return ret | writeJsonObjEnd(ctx); +} + +/* DataValue */ +ENCODE_JSON(DataValue) { + UA_Boolean hasValue = src->hasValue; + UA_Boolean hasStatus = src->hasStatus; + UA_Boolean hasSourceTimestamp = src->hasSourceTimestamp; + UA_Boolean hasSourcePicoseconds = src->hasSourcePicoseconds; + UA_Boolean hasServerTimestamp = src->hasServerTimestamp; + UA_Boolean hasServerPicoseconds = src->hasServerPicoseconds; + + status ret = writeJsonObjStart(ctx); + + if(hasValue) { + ret |= writeJsonKey(ctx, UA_JSONKEY_VALUE); + ret |= ENCODE_DIRECT_JSON(&src->value, Variant); + } + + if(hasStatus) { + ret |= writeJsonKey(ctx, UA_JSONKEY_STATUS); + ret |= ENCODE_DIRECT_JSON(&src->status, StatusCode); + } + + if(hasSourceTimestamp) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SOURCETIMESTAMP); + ret |= ENCODE_DIRECT_JSON(&src->sourceTimestamp, DateTime); + } + + if(hasSourcePicoseconds) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SOURCEPICOSECONDS); + ret |= ENCODE_DIRECT_JSON(&src->sourcePicoseconds, UInt16); + } + + if(hasServerTimestamp) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERTIMESTAMP); + ret |= ENCODE_DIRECT_JSON(&src->serverTimestamp, DateTime); + } + + if(hasServerPicoseconds) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERPICOSECONDS); + ret |= ENCODE_DIRECT_JSON(&src->serverPicoseconds, UInt16); + } + + return ret | writeJsonObjEnd(ctx); +} + +/* DiagnosticInfo */ +ENCODE_JSON(DiagnosticInfo) { + status ret = writeJsonObjStart(ctx); + + if(src->hasSymbolicId) { + ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOLICID); + ret |= ENCODE_DIRECT_JSON(&src->symbolicId, Int32); + } + + if(src->hasNamespaceUri) { + ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACEURI); + ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, Int32); + } + + if(src->hasLocalizedText) { + ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALIZEDTEXT); + ret |= ENCODE_DIRECT_JSON(&src->localizedText, Int32); + } + + if(src->hasLocale) { + ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); + ret |= ENCODE_DIRECT_JSON(&src->locale, Int32); + } + + if(src->hasAdditionalInfo) { + ret |= writeJsonKey(ctx, UA_JSONKEY_ADDITIONALINFO); + ret |= ENCODE_DIRECT_JSON(&src->additionalInfo, String); + } + + if(src->hasInnerStatusCode) { + ret |= writeJsonKey(ctx, UA_JSONKEY_INNERSTATUSCODE); + ret |= ENCODE_DIRECT_JSON(&src->innerStatusCode, StatusCode); + } + + if(src->hasInnerDiagnosticInfo && src->innerDiagnosticInfo) { + ret |= writeJsonKey(ctx, UA_JSONKEY_INNERDIAGNOSTICINFO); + ret |= encodeJsonJumpTable[UA_DATATYPEKIND_DIAGNOSTICINFO] + (ctx, src->innerDiagnosticInfo, NULL); + } + + return ret | writeJsonObjEnd(ctx); +} + +static status +encodeJsonStructure(CtxJson *ctx, const void *src, const UA_DataType *type) { + status ret = writeJsonObjStart(ctx); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + uintptr_t ptr = (uintptr_t) src; + u8 membersSize = type->membersSize; + for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) { + const UA_DataTypeMember *m = &type->members[i]; + const UA_DataType *mt = m->memberType; + + if(m->memberName != NULL && *m->memberName != 0) + ret |= writeJsonKey(ctx, m->memberName); + + if(!m->isArray) { + ptr += m->padding; + size_t memSize = mt->memSize; + ret |= encodeJsonJumpTable[mt->typeKind](ctx, (const void*) ptr, mt); + ptr += memSize; + } else { + ptr += m->padding; + const size_t length = *((const size_t*) ptr); + ptr += sizeof (size_t); + ret |= encodeJsonArray(ctx, *(void * const *)ptr, length, mt); + ptr += sizeof (void*); + } + } + + return ret | writeJsonObjEnd(ctx); +} + +static status +encodeJsonNotImplemented(const void *src, const UA_DataType *type, CtxJson *ctx) { + (void) src, (void) type, (void)ctx; + return UA_STATUSCODE_BADNOTIMPLEMENTED; +} + +const encodeJsonSignature encodeJsonJumpTable[UA_DATATYPEKINDS] = { + (encodeJsonSignature)Boolean_encodeJson, + (encodeJsonSignature)SByte_encodeJson, /* SByte */ + (encodeJsonSignature)Byte_encodeJson, + (encodeJsonSignature)Int16_encodeJson, /* Int16 */ + (encodeJsonSignature)UInt16_encodeJson, + (encodeJsonSignature)Int32_encodeJson, /* Int32 */ + (encodeJsonSignature)UInt32_encodeJson, + (encodeJsonSignature)Int64_encodeJson, /* Int64 */ + (encodeJsonSignature)UInt64_encodeJson, + (encodeJsonSignature)Float_encodeJson, + (encodeJsonSignature)Double_encodeJson, + (encodeJsonSignature)String_encodeJson, + (encodeJsonSignature)DateTime_encodeJson, /* DateTime */ + (encodeJsonSignature)Guid_encodeJson, + (encodeJsonSignature)ByteString_encodeJson, /* ByteString */ + (encodeJsonSignature)String_encodeJson, /* XmlElement */ + (encodeJsonSignature)NodeId_encodeJson, + (encodeJsonSignature)ExpandedNodeId_encodeJson, + (encodeJsonSignature)StatusCode_encodeJson, /* StatusCode */ + (encodeJsonSignature)QualifiedName_encodeJson, /* QualifiedName */ + (encodeJsonSignature)LocalizedText_encodeJson, + (encodeJsonSignature)ExtensionObject_encodeJson, + (encodeJsonSignature)DataValue_encodeJson, + (encodeJsonSignature)Variant_encodeJson, + (encodeJsonSignature)DiagnosticInfo_encodeJson, + (encodeJsonSignature)encodeJsonNotImplemented, /* Decimal */ + (encodeJsonSignature)Int32_encodeJson, /* Enum */ + (encodeJsonSignature)encodeJsonStructure, + (encodeJsonSignature)encodeJsonNotImplemented, /* Structure with optional fields */ + (encodeJsonSignature)encodeJsonNotImplemented, /* Union */ + (encodeJsonSignature)encodeJsonNotImplemented /* BitfieldCluster */ +}; + +UA_StatusCode +UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf, + const UA_EncodeJsonOptions *options) { + if(!src || !type) + return UA_STATUSCODE_BADINTERNALERROR; + + /* Allocate buffer */ + UA_Boolean allocated = false; + status res = UA_STATUSCODE_GOOD; + if(outBuf->length == 0) { + size_t len = UA_calcSizeJson(src, type, options); + res = UA_ByteString_allocBuffer(outBuf, len); + if(res != UA_STATUSCODE_GOOD) + return res; + allocated = true; + } + + /* Set up the context */ + CtxJson ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.pos = outBuf->data; + ctx.end = &outBuf->data[outBuf->length]; + ctx.depth = 0; + ctx.calcOnly = false; + ctx.useReversible = true; /* default */ + if(options) { + ctx.namespaceMapping = options->namespaceMapping; + ctx.serverUris = options->serverUris; + ctx.serverUrisSize = options->serverUrisSize; + ctx.useReversible = options->useReversible; + ctx.prettyPrint = options->prettyPrint; + ctx.unquotedKeys = options->unquotedKeys; + ctx.stringNodeIds = options->stringNodeIds; + } + + /* Encode */ + res = encodeJsonJumpTable[type->typeKind](&ctx, src, type); + + /* Clean up */ + if(res == UA_STATUSCODE_GOOD) + outBuf->length = (size_t)((uintptr_t)ctx.pos - (uintptr_t)outBuf->data); + else if(allocated) + UA_ByteString_clear(outBuf); + return res; +} + +UA_StatusCode +UA_print(const void *p, const UA_DataType *type, UA_String *output) { + if(!p || !type || !output) + return UA_STATUSCODE_BADINTERNALERROR; + + UA_EncodeJsonOptions options; + memset(&options, 0, sizeof(UA_EncodeJsonOptions)); + options.useReversible = true; + options.prettyPrint = true; + options.unquotedKeys = true; + options.stringNodeIds = true; + return UA_encodeJson(p, type, output, &options); +} + +/************/ +/* CalcSize */ +/************/ + +size_t +UA_calcSizeJson(const void *src, const UA_DataType *type, + const UA_EncodeJsonOptions *options) { + if(!src || !type) + return UA_STATUSCODE_BADINTERNALERROR; + + /* Set up the context */ + CtxJson ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.pos = NULL; + ctx.end = (const UA_Byte*)(uintptr_t)SIZE_MAX; + ctx.depth = 0; + ctx.useReversible = true; /* default */ + if(options) { + ctx.namespaceMapping = options->namespaceMapping; + ctx.serverUris = options->serverUris; + ctx.serverUrisSize = options->serverUrisSize; + ctx.useReversible = options->useReversible; + ctx.prettyPrint = options->prettyPrint; + ctx.unquotedKeys = options->unquotedKeys; + ctx.stringNodeIds = options->stringNodeIds; + } + + ctx.calcOnly = true; + + /* Encode */ + status ret = encodeJsonJumpTable[type->typeKind](&ctx, src, type); + if(ret != UA_STATUSCODE_GOOD) + return 0; + return (size_t)ctx.pos; +} + +/**********/ +/* Decode */ +/**********/ + +#define GET_TOKEN \ + size_t tokenSize = getTokenLength(&ctx->tokens[ctx->index]); \ + const char* tokenData = &ctx->json5[ctx->tokens[ctx->index].start]; \ + do {} while(0) + +#define CHECK_TOKEN_BOUNDS do { \ + if(ctx->index >= ctx->tokensSize) \ + return UA_STATUSCODE_BADDECODINGERROR; \ + } while(0) + +#define CHECK_NUMBER do { \ + if(currentTokenType(ctx) != CJ5_TOKEN_NUMBER) { \ + return UA_STATUSCODE_BADDECODINGERROR; \ + }} while(0) + +#define CHECK_BOOL do { \ + if(currentTokenType(ctx) != CJ5_TOKEN_BOOL) { \ + return UA_STATUSCODE_BADDECODINGERROR; \ + }} while(0) + +#define CHECK_STRING do { \ + if(currentTokenType(ctx) != CJ5_TOKEN_STRING) { \ + return UA_STATUSCODE_BADDECODINGERROR; \ + }} while(0) + +#define CHECK_OBJECT do { \ + if(currentTokenType(ctx) != CJ5_TOKEN_OBJECT) { \ + return UA_STATUSCODE_BADDECODINGERROR; \ + }} while(0) + +#define CHECK_NULL_SKIP do { \ + if(currentTokenType(ctx) == CJ5_TOKEN_NULL) { \ + ctx->index++; \ + return UA_STATUSCODE_GOOD; \ + }} while(0) + +/* Forward declarations*/ +#define DECODE_JSON(TYPE) static status \ + TYPE##_decodeJson(ParseCtx *ctx, UA_##TYPE *dst, \ + const UA_DataType *type) + +/* If ctx->index points to the beginning of an object, move the index to the + * next token after this object. Attention! The index can be moved after the + * last parsed token. So the array length has to be checked afterwards. */ +static void +skipObject(ParseCtx *ctx) { + unsigned int end = ctx->tokens[ctx->index].end; + do { + ctx->index++; + } while(ctx->index < ctx->tokensSize && + ctx->tokens[ctx->index].start < end); +} + +static status +Array_decodeJson(ParseCtx *ctx, void **dst, const UA_DataType *type); + +static status +Variant_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void *p, const UA_DataType *type); + +static UA_SByte +jsoneq(const char *json, const cj5_token *tok, const char *searchKey) { + /* TODO: necessary? + if(json == NULL + || tok == NULL + || searchKey == NULL) { + return -1; + } */ + + size_t len = getTokenLength(tok); + if(tok->type == CJ5_TOKEN_STRING && + strlen(searchKey) == len && + strncmp(json + tok->start, (const char*)searchKey, len) == 0) + return 0; + + return -1; +} + +DECODE_JSON(Boolean) { + CHECK_TOKEN_BOUNDS; + CHECK_BOOL; + GET_TOKEN; + + if(tokenSize == 4 && + (tokenData[0] | 32) == 't' && (tokenData[1] | 32) == 'r' && + (tokenData[2] | 32) == 'u' && (tokenData[3] | 32) == 'e') { + *dst = true; + } else if(tokenSize == 5 && + (tokenData[0] | 32) == 'f' && (tokenData[1] | 32) == 'a' && + (tokenData[2] | 32) == 'l' && (tokenData[3] | 32) == 's' && + (tokenData[4] | 32) == 'e') { + *dst = false; + } else { + return UA_STATUSCODE_BADDECODINGERROR; + } + + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +parseUnsignedInteger(const char *tokenData, size_t tokenSize, UA_UInt64 *dst) { + size_t len = parseUInt64(tokenData, tokenSize, dst); + if(len == 0) + return UA_STATUSCODE_BADDECODINGERROR; + + /* There must only be whitespace between the end of the parsed number and + * the end of the token */ + for(size_t i = len; i < tokenSize; i++) { + if(tokenData[i] != ' ' && tokenData[i] -'\t' >= 5) + return UA_STATUSCODE_BADDECODINGERROR; + } + + return UA_STATUSCODE_GOOD; +} + +static UA_StatusCode +parseSignedInteger(const char *tokenData, size_t tokenSize, UA_Int64 *dst) { + size_t len = parseInt64(tokenData, tokenSize, dst); + if(len == 0) + return UA_STATUSCODE_BADDECODINGERROR; + + /* There must only be whitespace between the end of the parsed number and + * the end of the token */ + for(size_t i = len; i < tokenSize; i++) { + if(tokenData[i] != ' ' && tokenData[i] -'\t' >= 5) + return UA_STATUSCODE_BADDECODINGERROR; + } + + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(Byte) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_UInt64 out = 0; + UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out > UA_BYTE_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_Byte)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(UInt16) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_UInt64 out = 0; + UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out > UA_UINT16_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_UInt16)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(UInt32) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_UInt64 out = 0; + UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out > UA_UINT32_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_UInt32)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(UInt64) { + CHECK_TOKEN_BOUNDS; + GET_TOKEN; + + UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, dst); + if(s != UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADDECODINGERROR; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(SByte) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_Int64 out = 0; + UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out < UA_SBYTE_MIN || out > UA_SBYTE_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_SByte)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(Int16) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_Int64 out = 0; + UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out < UA_INT16_MIN || out > UA_INT16_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_Int16)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(Int32) { + CHECK_TOKEN_BOUNDS; + CHECK_NUMBER; + GET_TOKEN; + + UA_Int64 out = 0; + UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); + if(s != UA_STATUSCODE_GOOD || out < UA_INT32_MIN || out > UA_INT32_MAX) + return UA_STATUSCODE_BADDECODINGERROR; + *dst = (UA_Int32)out; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(Int64) { + CHECK_TOKEN_BOUNDS; + GET_TOKEN; + + UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, dst); + if(s != UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADDECODINGERROR; + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +/* Either a STRING or NUMBER token */ +DECODE_JSON(Double) { + CHECK_TOKEN_BOUNDS; + GET_TOKEN; + + /* https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/ + * Maximum digit counts for select IEEE floating-point formats: 1074 + * Sanity check. + */ + if(tokenSize > 2000) + return UA_STATUSCODE_BADDECODINGERROR; + + cj5_token_type tokenType = currentTokenType(ctx); + + /* It could be a String with Nan, Infinity */ + if(tokenType == CJ5_TOKEN_STRING) { + ctx->index++; + + if(tokenSize == 8 && memcmp(tokenData, "Infinity", 8) == 0) { + *dst = INFINITY; + return UA_STATUSCODE_GOOD; + } + + if(tokenSize == 9 && memcmp(tokenData, "-Infinity", 9) == 0) { + /* workaround an MSVC 2013 issue */ + *dst = -INFINITY; + return UA_STATUSCODE_GOOD; + } + + if(tokenSize == 3 && memcmp(tokenData, "NaN", 3) == 0) { + *dst = NAN; + return UA_STATUSCODE_GOOD; + } + + if(tokenSize == 4 && memcmp(tokenData, "-NaN", 4) == 0) { + *dst = NAN; + return UA_STATUSCODE_GOOD; + } + + return UA_STATUSCODE_BADDECODINGERROR; + } + + if(tokenType != CJ5_TOKEN_NUMBER) + return UA_STATUSCODE_BADDECODINGERROR; + + size_t len = parseDouble(tokenData, tokenSize, dst); + if(len == 0) + return UA_STATUSCODE_BADDECODINGERROR; + + /* There must only be whitespace between the end of the parsed number and + * the end of the token */ + for(size_t i = len; i < tokenSize; i++) { + if(tokenData[i] != ' ' && tokenData[i] -'\t' >= 5) + return UA_STATUSCODE_BADDECODINGERROR; + } + + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(Float) { + UA_Double v = 0.0; + UA_StatusCode res = Double_decodeJson(ctx, &v, NULL); + *dst = (UA_Float)v; + return res; +} + +DECODE_JSON(Guid) { + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; + + /* Use the existing parsing routine if available */ + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + ctx->index++; + return UA_Guid_parse(dst, str); +} + +DECODE_JSON(String) { + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; + (void)tokenData; + + /* Empty string? */ + if(tokenSize == 0) { + dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; + dst->length = 0; + ctx->index++; + return UA_STATUSCODE_GOOD; + } + + /* The decoded utf8 is at most of the same length as the source string */ + char *outBuf = (char*)UA_malloc(tokenSize+1); + if(!outBuf) + return UA_STATUSCODE_BADOUTOFMEMORY; + + /* Decode the string */ + cj5_result r; + r.tokens = ctx->tokens; + r.num_tokens = (unsigned int)ctx->tokensSize; + r.json5 = ctx->json5; + unsigned int len = 0; + cj5_error_code err = cj5_get_str(&r, (unsigned int)ctx->index, outBuf, &len); + if(err != CJ5_ERROR_NONE) { + UA_free(outBuf); + return UA_STATUSCODE_BADDECODINGERROR; + } + + /* Set the output */ + dst->length = len; + if(dst->length > 0) { + dst->data = (UA_Byte*)outBuf; + } else { + dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; + UA_free(outBuf); + } + + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(ByteString) { + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; + + /* Empty bytestring? */ + if(tokenSize == 0) { + dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; + dst->length = 0; + } else { + size_t flen = 0; + unsigned char* unB64 = + UA_unbase64((const unsigned char*)tokenData, tokenSize, &flen); + if(unB64 == 0) + return UA_STATUSCODE_BADDECODINGERROR; + dst->data = (u8*)unB64; + dst->length = flen; + } + + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(LocalizedText) { + CHECK_OBJECT; + + DecodeEntry entries[2] = { + {UA_JSONKEY_LOCALE, &dst->locale, NULL, false, &UA_TYPES[UA_TYPES_STRING]}, + {UA_JSONKEY_TEXT, &dst->text, NULL, false, &UA_TYPES[UA_TYPES_STRING]} + }; + + return decodeFields(ctx, entries, 2); +} + +DECODE_JSON(QualifiedName) { + CHECK_OBJECT; + + DecodeEntry entries[2] = { + {UA_JSONKEY_NAME, &dst->name, NULL, false, &UA_TYPES[UA_TYPES_STRING]}, + {UA_JSONKEY_URI, &dst->namespaceIndex, NULL, false, &UA_TYPES[UA_TYPES_UINT16]} + }; + + return decodeFields(ctx, entries, 2); +} + +UA_FUNC_ATTR_WARN_UNUSED_RESULT status +lookAheadForKey(ParseCtx *ctx, const char *key, size_t *resultIndex) { + /* The current index must point to the beginning of an object. + * This has to be ensured by the caller. */ + UA_assert(currentTokenType(ctx) == CJ5_TOKEN_OBJECT); + + status ret = UA_STATUSCODE_BADNOTFOUND; + size_t oldIndex = ctx->index; /* Save index for later restore */ + unsigned int end = ctx->tokens[ctx->index].end; + ctx->index++; /* Move to the first key */ + while(ctx->index < ctx->tokensSize && + ctx->tokens[ctx->index].start < end) { + /* Key must be a string */ + UA_assert(currentTokenType(ctx) == CJ5_TOKEN_STRING); + + /* Move index to the value */ + ctx->index++; + + /* Value for the key must exist */ + UA_assert(ctx->index < ctx->tokensSize); + + /* Compare the key (previous index) */ + if(jsoneq(ctx->json5, &ctx->tokens[ctx->index-1], key) == 0) { + *resultIndex = ctx->index; /* Point result to the current index */ + ret = UA_STATUSCODE_GOOD; + break; + } + + skipObject(ctx); /* Jump over the value (can also be an array or object) */ + } + ctx->index = oldIndex; /* Restore the old index */ + return ret; +} + +static status +prepareDecodeNodeIdJson(ParseCtx *ctx, UA_NodeId *dst, + u8 *fieldCount, DecodeEntry *entries) { + UA_assert(currentTokenType(ctx) == CJ5_TOKEN_OBJECT); + + /* possible keys: Id, IdType, NamespaceIndex */ + /* Id must always be present */ + entries[*fieldCount].fieldName = UA_JSONKEY_ID; + entries[*fieldCount].found = false; + entries[*fieldCount].type = NULL; + entries[*fieldCount].function = NULL; + + /* IdType */ + size_t idIndex = 0; + status ret = lookAheadForKey(ctx, UA_JSONKEY_IDTYPE, &idIndex); + if(ret == UA_STATUSCODE_GOOD) { + size_t size = getTokenLength(&ctx->tokens[idIndex]); + if(size < 1) + return UA_STATUSCODE_BADDECODINGERROR; + + const char *idType = &ctx->json5[ctx->tokens[idIndex].start]; + + if(idType[0] == '2') { + dst->identifierType = UA_NODEIDTYPE_GUID; + entries[*fieldCount].fieldPointer = &dst->identifier.guid; + entries[*fieldCount].type = &UA_TYPES[UA_TYPES_GUID]; + } else if(idType[0] == '1') { + dst->identifierType = UA_NODEIDTYPE_STRING; + entries[*fieldCount].fieldPointer = &dst->identifier.string; + entries[*fieldCount].type = &UA_TYPES[UA_TYPES_STRING]; + } else if(idType[0] == '3') { + dst->identifierType = UA_NODEIDTYPE_BYTESTRING; + entries[*fieldCount].fieldPointer = &dst->identifier.byteString; + entries[*fieldCount].type = &UA_TYPES[UA_TYPES_BYTESTRING]; + } else { + return UA_STATUSCODE_BADDECODINGERROR; + } + + /* Id always present */ + (*fieldCount)++; + + entries[*fieldCount].fieldName = UA_JSONKEY_IDTYPE; + entries[*fieldCount].fieldPointer = NULL; + entries[*fieldCount].function = NULL; + entries[*fieldCount].found = false; + entries[*fieldCount].type = NULL; + + /* IdType */ + (*fieldCount)++; + } else { + dst->identifierType = UA_NODEIDTYPE_NUMERIC; + entries[*fieldCount].fieldPointer = &dst->identifier.numeric; + entries[*fieldCount].function = NULL; + entries[*fieldCount].found = false; + entries[*fieldCount].type = &UA_TYPES[UA_TYPES_UINT32]; + (*fieldCount)++; + } + + /* NodeId has a NamespaceIndex (the ExpandedNodeId specialization may + * overwrite this) */ + entries[*fieldCount].fieldName = UA_JSONKEY_NAMESPACE; + entries[*fieldCount].fieldPointer = &dst->namespaceIndex; + entries[*fieldCount].function = NULL; + entries[*fieldCount].found = false; + entries[*fieldCount].type = &UA_TYPES[UA_TYPES_UINT16]; + (*fieldCount)++; + + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(NodeId) { + /* Non-standard decoding of NodeIds from the string representation */ + if(currentTokenType(ctx) == CJ5_TOKEN_STRING) { + GET_TOKEN; + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + ctx->index++; + return UA_NodeId_parse(dst, str); + } + + /* Object representation */ + CHECK_OBJECT; + + u8 fieldCount = 0; + DecodeEntry entries[3]; + status ret = prepareDecodeNodeIdJson(ctx, dst, &fieldCount, entries); + if(ret != UA_STATUSCODE_GOOD) + return ret; + return decodeFields(ctx, entries, fieldCount); +} + +static status +decodeExpandedNodeIdNamespace(ParseCtx *ctx, void *dst, const UA_DataType *type) { + UA_ExpandedNodeId *en = (UA_ExpandedNodeId*)dst; + + /* Parse as a number */ + size_t oldIndex = ctx->index; + status ret = UInt16_decodeJson(ctx, &en->nodeId.namespaceIndex, NULL); + if(ret == UA_STATUSCODE_GOOD) + return ret; + + /* Parse as a string */ + ctx->index = oldIndex; /* Reset the index */ + ret = String_decodeJson(ctx, &en->namespaceUri, NULL); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + /* Replace with the index if the URI is found. Otherwise keep the string. */ + if(ctx->namespaceMapping) { + UA_StatusCode mapRes = + UA_NamespaceMapping_uri2Index(ctx->namespaceMapping, en->namespaceUri, + &en->nodeId.namespaceIndex); + if(mapRes == UA_STATUSCODE_GOOD) + UA_String_clear(&en->namespaceUri); + } + + return UA_STATUSCODE_GOOD; +} + +static status +decodeExpandedNodeIdServerUri(ParseCtx *ctx, void *dst, const UA_DataType *type) { + UA_ExpandedNodeId *en = (UA_ExpandedNodeId*)dst; + + /* Parse as a number */ + size_t oldIndex = ctx->index; + status ret = UInt32_decodeJson(ctx, &en->serverIndex, NULL); + if(ret == UA_STATUSCODE_GOOD) + return ret; + + /* Parse as a string */ + UA_String uri = UA_STRING_NULL; + ctx->index = oldIndex; /* Reset the index */ + ret = String_decodeJson(ctx, &uri, NULL); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + /* Try to translate the URI into an index */ + ret = UA_STATUSCODE_BADDECODINGERROR; + for(size_t i = 0; i < ctx->serverUrisSize; i++) { + if(UA_String_equal(&uri, &ctx->serverUris[i])) { + en->serverIndex = (UA_UInt32)i; + ret = UA_STATUSCODE_GOOD; + break; + } + } + + UA_String_clear(&uri); + return ret; +} + +DECODE_JSON(ExpandedNodeId) { + /* Non-standard decoding of ExpandedNodeIds from the string representation */ + if(currentTokenType(ctx) == CJ5_TOKEN_STRING) { + GET_TOKEN; + UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; + ctx->index++; + return UA_ExpandedNodeId_parse(dst, str); + } + + /* Object representation */ + CHECK_OBJECT; + + u8 fieldCount = 0; + DecodeEntry entries[4]; + status ret = prepareDecodeNodeIdJson(ctx, &dst->nodeId, &fieldCount, entries); + if(ret != UA_STATUSCODE_GOOD) + return ret; + + /* Overwrite the namespace entry */ + fieldCount--; + entries[fieldCount].fieldPointer = dst; + entries[fieldCount].function = decodeExpandedNodeIdNamespace; + entries[fieldCount].type = NULL; + fieldCount++; + + entries[fieldCount].fieldName = UA_JSONKEY_SERVERURI; + entries[fieldCount].fieldPointer = dst; + entries[fieldCount].function = decodeExpandedNodeIdServerUri; + entries[fieldCount].found = false; + entries[fieldCount].type = NULL; + fieldCount++; + + return decodeFields(ctx, entries, fieldCount); +} + +DECODE_JSON(DateTime) { + CHECK_TOKEN_BOUNDS; + CHECK_STRING; + GET_TOKEN; + + /* The last character has to be 'Z'. We can omit some length checks later on + * because we know the atoi functions stop before the 'Z'. */ + if(tokenSize == 0 || tokenData[tokenSize-1] != 'Z') + return UA_STATUSCODE_BADDECODINGERROR; + + struct mytm dts; + memset(&dts, 0, sizeof(dts)); + + size_t pos = 0; + size_t len; + + /* Parse the year. The ISO standard asks for four digits. But we accept up + * to five with an optional plus or minus in front due to the range of the + * DateTime 64bit integer. But in that case we require the year and the + * month to be separated by a '-'. Otherwise we cannot know where the month + * starts. */ + if(tokenData[0] == '-' || tokenData[0] == '+') + pos++; + UA_Int64 year = 0; + len = parseInt64(&tokenData[pos], 5, &year); + pos += len; + if(len != 4 && tokenData[pos] != '-') + return UA_STATUSCODE_BADDECODINGERROR; + if(tokenData[0] == '-') + year = -year; + dts.tm_year = (UA_Int16)year - 1900; + if(tokenData[pos] == '-') + pos++; + + /* Parse the month */ + UA_UInt64 month = 0; + len = parseUInt64(&tokenData[pos], 2, &month); + pos += len; + UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR); + dts.tm_mon = (UA_UInt16)month - 1; + if(tokenData[pos] == '-') + pos++; + + /* Parse the day and check the T between date and time */ + UA_UInt64 day = 0; + len = parseUInt64(&tokenData[pos], 2, &day); + pos += len; + UA_CHECK(len == 2 || tokenData[pos] != 'T', + return UA_STATUSCODE_BADDECODINGERROR); + dts.tm_mday = (UA_UInt16)day; + pos++; + + /* Parse the hour */ + UA_UInt64 hour = 0; + len = parseUInt64(&tokenData[pos], 2, &hour); + pos += len; + UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR); + dts.tm_hour = (UA_UInt16)hour; + if(tokenData[pos] == ':') + pos++; + + /* Parse the minute */ + UA_UInt64 min = 0; + len = parseUInt64(&tokenData[pos], 2, &min); + pos += len; + UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR); + dts.tm_min = (UA_UInt16)min; + if(tokenData[pos] == ':') + pos++; + + /* Parse the second */ + UA_UInt64 sec = 0; + len = parseUInt64(&tokenData[pos], 2, &sec); + pos += len; + UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR); + dts.tm_sec = (UA_UInt16)sec; + + /* Compute the seconds since the Unix epoch */ + long long sinceunix = __tm_to_secs(&dts); + + /* Are we within the range that can be represented? */ + long long sinceunix_min = + (long long)(UA_INT64_MIN / UA_DATETIME_SEC) - + (long long)(UA_DATETIME_UNIX_EPOCH / UA_DATETIME_SEC) - + (long long)1; /* manual correction due to rounding */ + long long sinceunix_max = (long long) + ((UA_INT64_MAX - UA_DATETIME_UNIX_EPOCH) / UA_DATETIME_SEC); + if(sinceunix < sinceunix_min || sinceunix > sinceunix_max) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Convert to DateTime. Add or subtract one extra second here to prevent + * underflow/overflow. This is reverted once the fractional part has been + * added. */ + sinceunix -= (sinceunix > 0) ? 1 : -1; + UA_DateTime dt = (UA_DateTime) + (sinceunix + (UA_DATETIME_UNIX_EPOCH / UA_DATETIME_SEC)) * UA_DATETIME_SEC; + + /* Parse the fraction of the second if defined */ + if(tokenData[pos] == ',' || tokenData[pos] == '.') { + pos++; + double frac = 0.0; + double denom = 0.1; + while(pos < tokenSize && + tokenData[pos] >= '0' && tokenData[pos] <= '9') { + frac += denom * (tokenData[pos] - '0'); + denom *= 0.1; + pos++; + } + frac += 0.00000005; /* Correct rounding when converting to integer */ + dt += (UA_DateTime)(frac * UA_DATETIME_SEC); + } + + /* Remove the underflow/overflow protection (see above) */ + if(sinceunix > 0) { + if(dt > UA_INT64_MAX - UA_DATETIME_SEC) + return UA_STATUSCODE_BADDECODINGERROR; + dt += UA_DATETIME_SEC; + } else { + if(dt < UA_INT64_MIN + UA_DATETIME_SEC) + return UA_STATUSCODE_BADDECODINGERROR; + dt -= UA_DATETIME_SEC; + } + + /* We must be at the end of the string (ending with 'Z' as checked above) */ + if(pos != tokenSize - 1) + return UA_STATUSCODE_BADDECODINGERROR; + + *dst = dt; + + ctx->index++; + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(StatusCode) { + return UInt32_decodeJson(ctx, dst, NULL); +} + +/* Get type type encoded by the ExtensionObject at ctx->index. + * Returns NULL if that fails (type unknown or otherwise). */ +static const UA_DataType * +getExtensionObjectType(ParseCtx *ctx) { + if(currentTokenType(ctx) != CJ5_TOKEN_OBJECT) + return NULL; + + /* Get the type NodeId index */ + size_t typeIdIndex = 0; + UA_StatusCode ret = lookAheadForKey(ctx, UA_JSONKEY_TYPEID, &typeIdIndex); + if(ret != UA_STATUSCODE_GOOD) + return NULL; + + size_t oldIndex = ctx->index; + ctx->index = (UA_UInt16)typeIdIndex; + + /* Decode the type NodeId */ + UA_NodeId typeId; + UA_NodeId_init(&typeId); + ret = NodeId_decodeJson(ctx, &typeId, &UA_TYPES[UA_TYPES_NODEID]); + ctx->index = oldIndex; + if(ret != UA_STATUSCODE_GOOD) { + UA_NodeId_clear(&typeId); /* We don't have the global cleanup */ + return NULL; + } + + /* Lookup an return */ + const UA_DataType *type = UA_findDataTypeWithCustom(&typeId, ctx->customTypes); + UA_NodeId_clear(&typeId); + return type; +} + +/* Check if all array members are ExtensionObjects of the same type. Return this + * type or NULL. */ +static const UA_DataType * +getArrayUnwrapType(ParseCtx *ctx, size_t arrayIndex) { + UA_assert(ctx->tokens[arrayIndex].type == CJ5_TOKEN_ARRAY); + + /* Save index to restore later */ + size_t oldIndex = ctx->index; + ctx->index = arrayIndex; + + /* Return early for empty arrays */ + size_t length = (size_t)ctx->tokens[ctx->index].size; + if(length == 0) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Go to first array member */ + ctx->index++; + + /* Lookup the type for the first array member */ + UA_NodeId typeId; + UA_NodeId_init(&typeId); + const UA_DataType *typeOfBody = getExtensionObjectType(ctx); + if(!typeOfBody) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* The content is a builtin type that could have been directly encoded in + * the Variant, there was no need to wrap in an ExtensionObject. But this + * means for us, that somebody made an extra effort to explicitly get an + * ExtensionObject. So we keep it. As an added advantage we will generate + * the same JSON again when encoding again. */ + UA_Boolean isBuiltin = (typeOfBody->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); + if(isBuiltin) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Get the typeId index for faster comparison below. + * Cannot fail as getExtensionObjectType already looked this up. */ + size_t typeIdIndex = 0; + UA_StatusCode ret = lookAheadForKey(ctx, UA_JSONKEY_TYPEID, &typeIdIndex); + (void)ret; + UA_assert(ret == UA_STATUSCODE_GOOD); + const char* typeIdData = &ctx->json5[ctx->tokens[typeIdIndex].start]; + size_t typeIdSize = getTokenLength(&ctx->tokens[typeIdIndex]); + + /* Loop over all members and check whether they can be unwrapped */ + for(size_t i = 0; i < length; i++) { + /* Array element must be an object */ + if(currentTokenType(ctx) != CJ5_TOKEN_OBJECT) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Check for non-JSON encoding */ + size_t encIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_ENCODING, &encIndex); + if(ret == UA_STATUSCODE_GOOD) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Get the type NodeId index */ + size_t memberTypeIdIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_TYPEID, &memberTypeIdIndex); + if(ret != UA_STATUSCODE_GOOD) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Is it the same type? Compare raw NodeId string */ + const char* memberTypeIdData = &ctx->json5[ctx->tokens[memberTypeIdIndex].start]; + size_t memberTypeIdSize = getTokenLength(&ctx->tokens[memberTypeIdIndex]); + if(typeIdSize != memberTypeIdSize || + memcmp(typeIdData, memberTypeIdData, typeIdSize) != 0) { + ctx->index = oldIndex; /* Restore the index */ + return NULL; + } + + /* Skip to the next array member */ + skipObject(ctx); + } + + ctx->index = oldIndex; /* Restore the index */ + return typeOfBody; +} + +static status +Array_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void **dst, const UA_DataType *type) { + size_t *size_ptr = (size_t*) dst - 1; /* Save the length pointer of the array */ + size_t length = (size_t)ctx->tokens[ctx->index].size; + + /* Known from the previous unwrapping-check */ + UA_assert(currentTokenType(ctx) == CJ5_TOKEN_ARRAY); + UA_assert(length > 0); + + ctx->index++; /* Go to first array member */ + + /* Allocate memory */ + *dst = UA_calloc(length, type->memSize); + if(*dst == NULL) + return UA_STATUSCODE_BADOUTOFMEMORY; + + /* Decode array members */ + uintptr_t ptr = (uintptr_t)*dst; + for(size_t i = 0; i < length; i++) { + UA_assert(ctx->tokens[ctx->index].type == CJ5_TOKEN_OBJECT); + + /* Get the body field and decode it */ + DecodeEntry entries[3] = { + {UA_JSONKEY_TYPEID, NULL, NULL, false, NULL}, + {UA_JSONKEY_BODY, (void*)ptr, NULL, false, type}, + {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL} + }; + status ret = decodeFields(ctx, entries, 3); /* Also skips to the next object */ + if(ret != UA_STATUSCODE_GOOD) { + UA_Array_delete(*dst, i+1, type); + *dst = NULL; + return ret; + } + ptr += type->memSize; + } + + *size_ptr = length; /* All good, set the size */ + return UA_STATUSCODE_GOOD; +} + +static status +decodeVariantBodyWithType(ParseCtx *ctx, UA_Variant *dst, size_t bodyIndex, + size_t *dimIndex, const UA_DataType *type) { + /* Value is an array? */ + UA_Boolean isArray = (ctx->tokens[bodyIndex].type == CJ5_TOKEN_ARRAY); + + /* TODO: Handling of null-arrays (length -1) needs to be clarified + * + * if(tokenIsNull(ctx, bodyIndex)) { + * isArray = true; + * dst->arrayLength = 0; + * } */ + + /* No array but has dimension -> error */ + if(!isArray && dimIndex) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Get the datatype of the content. The type must be a builtin data type. + * All not-builtin types are wrapped in an ExtensionObject. */ + if(type->typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO) + return UA_STATUSCODE_BADDECODINGERROR; + + /* A variant cannot contain a variant. But it can contain an array of + * variants */ + if(type->typeKind == UA_DATATYPEKIND_VARIANT && !isArray) + return UA_STATUSCODE_BADDECODINGERROR; + + ctx->depth++; + ctx->index = bodyIndex; + + /* Decode an array */ + status res = UA_STATUSCODE_GOOD; + if(isArray) { + /* Try to unwrap ExtensionObjects in the array. + * The members must all have the same type. */ + const UA_DataType *unwrapType = NULL; + if(type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT] && + (unwrapType = getArrayUnwrapType(ctx, bodyIndex))) { + dst->type = unwrapType; + res = Array_decodeJsonUnwrapExtensionObject(ctx, &dst->data, unwrapType); + } else { + dst->type = type; + res = Array_decodeJson(ctx, &dst->data, type); + } + + /* Decode array dimensions */ + if(dimIndex) { + ctx->index = *dimIndex; + res |= Array_decodeJson(ctx, (void**)&dst->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]); + } + ctx->depth--; + return res; + } + + /* Decode a value wrapped in an ExtensionObject */ + if(type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { + res = Variant_decodeJsonUnwrapExtensionObject(ctx, dst, NULL); + goto out; + } + + /* Allocate Memory for Body */ + dst->data = UA_new(type); + if(!dst->data) { + res = UA_STATUSCODE_BADOUTOFMEMORY; + goto out; + } + + /* Decode the body */ + dst->type = type; + if(ctx->tokens[ctx->index].type != CJ5_TOKEN_NULL) + res = decodeJsonJumpTable[type->typeKind](ctx, dst->data, type); + + out: + ctx->depth--; + return res; +} + +DECODE_JSON(Variant) { + CHECK_NULL_SKIP; /* Treat null as an empty variant */ + CHECK_OBJECT; + + /* First search for the variant type in the json object. */ + size_t typeIndex = 0; + status ret = lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); + if(ret != UA_STATUSCODE_GOOD) { + skipObject(ctx); + return UA_STATUSCODE_GOOD; + } + + /* Parse the type */ + if(ctx->tokens[typeIndex].type != CJ5_TOKEN_NUMBER) + return UA_STATUSCODE_BADDECODINGERROR; + UA_UInt64 idType = 0; + size_t len = parseUInt64(&ctx->json5[ctx->tokens[typeIndex].start], + getTokenLength(&ctx->tokens[typeIndex]), &idType); + if(len == 0) + return UA_STATUSCODE_BADDECODINGERROR; + + /* A NULL Variant */ + if(idType == 0) { + skipObject(ctx); + return UA_STATUSCODE_GOOD; + } + + /* Set the type */ + UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idType); + type = UA_findDataTypeWithCustom(&typeNodeId, ctx->customTypes); + if(!type) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Search for body */ + size_t bodyIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_BODY, &bodyIndex); + if(ret != UA_STATUSCODE_GOOD) + return UA_STATUSCODE_BADDECODINGERROR; + + /* Search for the dimensions */ + size_t *dimPtr = NULL; + size_t dimIndex = 0; + ret = lookAheadForKey(ctx, UA_JSONKEY_DIMENSION, &dimIndex); + if(ret == UA_STATUSCODE_GOOD && ctx->tokens[dimIndex].size > 0) + dimPtr = &dimIndex; + + /* Decode the body */ + return decodeVariantBodyWithType(ctx, dst, bodyIndex, dimPtr, type); +} + +DECODE_JSON(DataValue) { + CHECK_NULL_SKIP; /* Treat a null value as an empty DataValue */ + CHECK_OBJECT; + + DecodeEntry entries[6] = { + {UA_JSONKEY_VALUE, &dst->value, NULL, false, &UA_TYPES[UA_TYPES_VARIANT]}, + {UA_JSONKEY_STATUS, &dst->status, NULL, false, &UA_TYPES[UA_TYPES_STATUSCODE]}, + {UA_JSONKEY_SOURCETIMESTAMP, &dst->sourceTimestamp, NULL, false, &UA_TYPES[UA_TYPES_DATETIME]}, + {UA_JSONKEY_SOURCEPICOSECONDS, &dst->sourcePicoseconds, NULL, false, &UA_TYPES[UA_TYPES_UINT16]}, + {UA_JSONKEY_SERVERTIMESTAMP, &dst->serverTimestamp, NULL, false, &UA_TYPES[UA_TYPES_DATETIME]}, + {UA_JSONKEY_SERVERPICOSECONDS, &dst->serverPicoseconds, NULL, false, &UA_TYPES[UA_TYPES_UINT16]} + }; + + status ret = decodeFields(ctx, entries, 6); + dst->hasValue = entries[0].found; + dst->hasStatus = entries[1].found; + dst->hasSourceTimestamp = entries[2].found; + dst->hasSourcePicoseconds = entries[3].found; + dst->hasServerTimestamp = entries[4].found; + dst->hasServerPicoseconds = entries[5].found; + return ret; +} + +/* Move the entire current token into the target bytestring */ +static UA_StatusCode +tokenToByteString(ParseCtx *ctx, UA_ByteString *p, const UA_DataType *type) { + GET_TOKEN; + UA_StatusCode res = UA_ByteString_allocBuffer(p, tokenSize); + if(res != UA_STATUSCODE_GOOD) + return res; + memcpy(p->data, tokenData, tokenSize); + skipObject(ctx); + return UA_STATUSCODE_GOOD; +} + +DECODE_JSON(ExtensionObject) { + CHECK_NULL_SKIP; /* Treat a null value as an empty DataValue */ + CHECK_OBJECT; + + /* Empty object -> Null ExtensionObject */ + if(ctx->tokens[ctx->index].size == 0) { + ctx->index++; /* Skip the empty ExtensionObject */ + return UA_STATUSCODE_GOOD; + } + + /* Search for non-JSON encoding */ + UA_UInt64 encoding = 0; + size_t encIndex = 0; + status ret = lookAheadForKey(ctx, UA_JSONKEY_ENCODING, &encIndex); + if(ret == UA_STATUSCODE_GOOD) { + const char *extObjEncoding = &ctx->json5[ctx->tokens[encIndex].start]; + size_t len = parseUInt64(extObjEncoding, getTokenLength(&ctx->tokens[encIndex]), + &encoding); + if(len == 0) + return UA_STATUSCODE_BADDECODINGERROR; + } + + /* Lookup the DataType for the ExtensionObject if the body can be decoded */ + const UA_DataType *typeOfBody = (encoding == 0) ? getExtensionObjectType(ctx) : NULL; + + /* Keep the encoded body */ + if(!typeOfBody) { + DecodeEntry entries[3] = { + {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL}, + {UA_JSONKEY_TYPEID, &dst->content.encoded.typeId, NULL, false, &UA_TYPES[UA_TYPES_NODEID]}, + {UA_JSONKEY_BODY, &dst->content.encoded.body, NULL, false, &UA_TYPES[UA_TYPES_STRING]} + }; + + if(encoding == 0) { + entries[2].function = (decodeJsonSignature)tokenToByteString; + dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; /* ByteString in Json Body */ + } else if(encoding == 1) { + dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; /* ByteString in Json Body */ + } else if(encoding == 2) { + dst->encoding = UA_EXTENSIONOBJECT_ENCODED_XML; /* XmlElement in Json Body */ + } else { + return UA_STATUSCODE_BADDECODINGERROR; + } + return decodeFields(ctx, entries, 3); + } + + /* Allocate memory for the decoded data */ + dst->content.decoded.data = UA_new(typeOfBody); + if(!dst->content.decoded.data) + return UA_STATUSCODE_BADOUTOFMEMORY; + + /* Set type */ + dst->content.decoded.type = typeOfBody; + dst->encoding = UA_EXTENSIONOBJECT_DECODED; + + /* Decode body */ + DecodeEntry entries[3] = { + {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL}, + {UA_JSONKEY_TYPEID, NULL, NULL, false, NULL}, + {UA_JSONKEY_BODY, dst->content.decoded.data, NULL, false, typeOfBody} + }; + return decodeFields(ctx, entries, 3); +} + +static status +Variant_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void *p, const UA_DataType *type) { + (void) type; + UA_Variant *dst = (UA_Variant*)p; + + /* ExtensionObject with null body */ + if(currentTokenType(ctx) == CJ5_TOKEN_NULL) { + dst->data = UA_ExtensionObject_new(); + dst->type = &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]; + ctx->index++; + return UA_STATUSCODE_GOOD; + } + + /* Decode the ExtensionObject */ + UA_ExtensionObject eo; + UA_ExtensionObject_init(&eo); + UA_StatusCode ret = ExtensionObject_decodeJson(ctx, &eo, NULL); + if(ret != UA_STATUSCODE_GOOD) { + UA_ExtensionObject_clear(&eo); /* We don't have the global cleanup */ + return ret; + } + + /* The content is still encoded, cannot unwrap */ + if(eo.encoding != UA_EXTENSIONOBJECT_DECODED) + goto use_eo; + + /* The content is a builtin type that could have been directly encoded in + * the Variant, there was no need to wrap in an ExtensionObject. But this + * means for us, that somebody made an extra effort to explicitly get an + * ExtensionObject. So we keep it. As an added advantage we will generate + * the same JSON again when encoding again. */ + UA_Boolean isBuiltin = + (eo.content.decoded.type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); + if(isBuiltin) + goto use_eo; + + /* Unwrap the ExtensionObject */ + dst->data = eo.content.decoded.data; + dst->type = eo.content.decoded.type; + return UA_STATUSCODE_GOOD; + + use_eo: + /* Don't unwrap */ + dst->data = UA_new(&UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); + if(!dst->data) { + UA_ExtensionObject_clear(&eo); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + dst->type = &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]; + *(UA_ExtensionObject*)dst->data = eo; + return UA_STATUSCODE_GOOD; +} + +status +DiagnosticInfoInner_decodeJson(ParseCtx* ctx, void* dst, const UA_DataType* type); + +DECODE_JSON(DiagnosticInfo) { + CHECK_NULL_SKIP; /* Treat a null value as an empty DiagnosticInfo */ + CHECK_OBJECT; + + DecodeEntry entries[7] = { + {UA_JSONKEY_SYMBOLICID, &dst->symbolicId, NULL, false, &UA_TYPES[UA_TYPES_INT32]}, + {UA_JSONKEY_NAMESPACEURI, &dst->namespaceUri, NULL, false, &UA_TYPES[UA_TYPES_INT32]}, + {UA_JSONKEY_LOCALIZEDTEXT, &dst->localizedText, NULL, false, &UA_TYPES[UA_TYPES_INT32]}, + {UA_JSONKEY_LOCALE, &dst->locale, NULL, false, &UA_TYPES[UA_TYPES_INT32]}, + {UA_JSONKEY_ADDITIONALINFO, &dst->additionalInfo, NULL, false, &UA_TYPES[UA_TYPES_STRING]}, + {UA_JSONKEY_INNERSTATUSCODE, &dst->innerStatusCode, NULL, false, &UA_TYPES[UA_TYPES_STATUSCODE]}, + {UA_JSONKEY_INNERDIAGNOSTICINFO, &dst->innerDiagnosticInfo, DiagnosticInfoInner_decodeJson, false, NULL} + }; + status ret = decodeFields(ctx, entries, 7); + + dst->hasSymbolicId = entries[0].found; + dst->hasNamespaceUri = entries[1].found; + dst->hasLocalizedText = entries[2].found; + dst->hasLocale = entries[3].found; + dst->hasAdditionalInfo = entries[4].found; + dst->hasInnerStatusCode = entries[5].found; + dst->hasInnerDiagnosticInfo = entries[6].found; + return ret; +} + +status +DiagnosticInfoInner_decodeJson(ParseCtx* ctx, void* dst, const UA_DataType* type) { + UA_DiagnosticInfo *inner = (UA_DiagnosticInfo*) + UA_calloc(1, sizeof(UA_DiagnosticInfo)); + if(!inner) + return UA_STATUSCODE_BADOUTOFMEMORY; + UA_DiagnosticInfo **dst2 = (UA_DiagnosticInfo**)dst; + *dst2 = inner; /* Copy new Pointer do dest */ + return DiagnosticInfo_decodeJson(ctx, inner, type); +} + +status +decodeFields(ParseCtx *ctx, DecodeEntry *entries, size_t entryCount) { + CHECK_TOKEN_BOUNDS; + CHECK_NULL_SKIP; /* null is treated like an empty object */ + + if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION - 1) + return UA_STATUSCODE_BADENCODINGERROR; + + /* Keys and values are counted separately */ + CHECK_OBJECT; + UA_assert(ctx->tokens[ctx->index].size % 2 == 0); + size_t keyCount = (size_t)(ctx->tokens[ctx->index].size) / 2; + + ctx->index++; /* Go to first key - or jump after the empty object */ + ctx->depth++; + + status ret = UA_STATUSCODE_GOOD; + for(size_t key = 0; key < keyCount; key++) { + /* Key must be a string */ + UA_assert(ctx->index < ctx->tokensSize); + UA_assert(currentTokenType(ctx) == CJ5_TOKEN_STRING); + + /* Search for the decoding entry matching the key. Start at the key + * index to speed-up the case where they key-order is the same as the + * entry-order. */ + DecodeEntry *entry = NULL; + for(size_t i = key; i < key + entryCount; i++) { + size_t ii = i; + while(ii >= entryCount) + ii -= entryCount; + + /* Compare the key */ + if(jsoneq(ctx->json5, &ctx->tokens[ctx->index], + entries[ii].fieldName) != 0) + continue; + + /* Key was already used -> duplicate, abort */ + if(entries[ii].found) { + ctx->depth--; + return UA_STATUSCODE_BADDECODINGERROR; + } + + /* Found the key */ + entries[ii].found = true; + entry = &entries[ii]; + break; + } + + /* The key is unknown */ + if(!entry) { + ret = UA_STATUSCODE_BADDECODINGERROR; + break; + } + + /* Go from key to value */ + ctx->index++; + UA_assert(ctx->index < ctx->tokensSize); + + /* An entry that was expected but shall not be decoded. + * Jump over the value. */ + if(!entry->function && !entry->type) { + skipObject(ctx); + continue; + } + + /* A null-value, skip the decoding (the value is already initialized) */ + if(currentTokenType(ctx) == CJ5_TOKEN_NULL && !entry->function) { + ctx->index++; /* skip null value */ + continue; + } + + /* Decode. This also moves to the next key or right after the object for + * the last value. */ + decodeJsonSignature decodeFunc = (entry->function) ? + entry->function : decodeJsonJumpTable[entry->type->typeKind]; + ret = decodeFunc(ctx, entry->fieldPointer, entry->type); + if(ret != UA_STATUSCODE_GOOD) + break; + } + + ctx->depth--; + return ret; +} + +static status +Array_decodeJson(ParseCtx *ctx, void **dst, const UA_DataType *type) { + /* Save the length of the array */ + size_t *size_ptr = (size_t*) dst - 1; + + if(currentTokenType(ctx) != CJ5_TOKEN_ARRAY) + return UA_STATUSCODE_BADDECODINGERROR; + + size_t length = (size_t)ctx->tokens[ctx->index].size; + + ctx->index++; /* Go to first array member or to the first element after + * the array (if empty) */ + + /* Return early for empty arrays */ + if(length == 0) { + *size_ptr = length; + *dst = UA_EMPTY_ARRAY_SENTINEL; + return UA_STATUSCODE_GOOD; + } + + /* Allocate memory */ + *dst = UA_calloc(length, type->memSize); + if(*dst == NULL) + return UA_STATUSCODE_BADOUTOFMEMORY; + + /* Decode array members */ + uintptr_t ptr = (uintptr_t)*dst; + for(size_t i = 0; i < length; ++i) { + if(ctx->tokens[ctx->index].type != CJ5_TOKEN_NULL) { + status ret = decodeJsonJumpTable[type->typeKind](ctx, (void*)ptr, type); + if(ret != UA_STATUSCODE_GOOD) { + UA_Array_delete(*dst, i+1, type); + *dst = NULL; + return ret; + } + } else { + ctx->index++; + } + ptr += type->memSize; + } + + *size_ptr = length; /* All good, set the size */ + return UA_STATUSCODE_GOOD; +} + +static status +decodeJsonStructure(ParseCtx *ctx, void *dst, const UA_DataType *type) { + /* Check the recursion limit */ + if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION - 1) + return UA_STATUSCODE_BADENCODINGERROR; + ctx->depth++; + + uintptr_t ptr = (uintptr_t)dst; + status ret = UA_STATUSCODE_GOOD; + u8 membersSize = type->membersSize; + UA_STACKARRAY(DecodeEntry, entries, membersSize); + for(size_t i = 0; i < membersSize; ++i) { + const UA_DataTypeMember *m = &type->members[i]; + const UA_DataType *mt = m->memberType; + entries[i].type = mt; + entries[i].fieldName = m->memberName; + entries[i].found = false; + if(!m->isArray) { + ptr += m->padding; + entries[i].fieldPointer = (void*)ptr; + entries[i].function = NULL; + ptr += mt->memSize; + } else { + ptr += m->padding; + ptr += sizeof(size_t); + entries[i].fieldPointer = (void*)ptr; + entries[i].function = (decodeJsonSignature)Array_decodeJson; + ptr += sizeof(void*); + } + } + + ret = decodeFields(ctx, entries, membersSize); + + if(ctx->depth == 0) + return UA_STATUSCODE_BADENCODINGERROR; + ctx->depth--; + return ret; +} + +static status +decodeJsonNotImplemented(ParseCtx *ctx, void *dst, const UA_DataType *type) { + (void)dst, (void)type, (void)ctx; + return UA_STATUSCODE_BADNOTIMPLEMENTED; +} + +const decodeJsonSignature decodeJsonJumpTable[UA_DATATYPEKINDS] = { + (decodeJsonSignature)Boolean_decodeJson, + (decodeJsonSignature)SByte_decodeJson, /* SByte */ + (decodeJsonSignature)Byte_decodeJson, + (decodeJsonSignature)Int16_decodeJson, /* Int16 */ + (decodeJsonSignature)UInt16_decodeJson, + (decodeJsonSignature)Int32_decodeJson, /* Int32 */ + (decodeJsonSignature)UInt32_decodeJson, + (decodeJsonSignature)Int64_decodeJson, /* Int64 */ + (decodeJsonSignature)UInt64_decodeJson, + (decodeJsonSignature)Float_decodeJson, + (decodeJsonSignature)Double_decodeJson, + (decodeJsonSignature)String_decodeJson, + (decodeJsonSignature)DateTime_decodeJson, /* DateTime */ + (decodeJsonSignature)Guid_decodeJson, + (decodeJsonSignature)ByteString_decodeJson, /* ByteString */ + (decodeJsonSignature)String_decodeJson, /* XmlElement */ + (decodeJsonSignature)NodeId_decodeJson, + (decodeJsonSignature)ExpandedNodeId_decodeJson, + (decodeJsonSignature)StatusCode_decodeJson, /* StatusCode */ + (decodeJsonSignature)QualifiedName_decodeJson, /* QualifiedName */ + (decodeJsonSignature)LocalizedText_decodeJson, + (decodeJsonSignature)ExtensionObject_decodeJson, + (decodeJsonSignature)DataValue_decodeJson, + (decodeJsonSignature)Variant_decodeJson, + (decodeJsonSignature)DiagnosticInfo_decodeJson, + (decodeJsonSignature)decodeJsonNotImplemented, /* Decimal */ + (decodeJsonSignature)Int32_decodeJson, /* Enum */ + (decodeJsonSignature)decodeJsonStructure, + (decodeJsonSignature)decodeJsonNotImplemented, /* Structure with optional fields */ + (decodeJsonSignature)decodeJsonNotImplemented, /* Union */ + (decodeJsonSignature)decodeJsonNotImplemented /* BitfieldCluster */ +}; + +status +tokenize(ParseCtx *ctx, const UA_ByteString *src, size_t tokensSize, + size_t *decodedLength) { + /* Tokenize */ + cj5_options options; + options.stop_early = (decodedLength != NULL); + cj5_result r = cj5_parse((char*)src->data, (unsigned int)src->length, + ctx->tokens, (unsigned int)tokensSize, &options); + + /* Handle overflow error by allocating the number of tokens the parser would + * have needed */ + if(r.error == CJ5_ERROR_OVERFLOW && + tokensSize != r.num_tokens) { + ctx->tokens = (cj5_token*) + UA_malloc(sizeof(cj5_token) * r.num_tokens); + if(!ctx->tokens) + return UA_STATUSCODE_BADOUTOFMEMORY; + return tokenize(ctx, src, r.num_tokens, decodedLength); + } + + /* Cannot recover from other errors */ + if(r.error != CJ5_ERROR_NONE) + return UA_STATUSCODE_BADDECODINGERROR; + + if(decodedLength) + *decodedLength = ctx->tokens[0].end + 1; + + /* Set up the context */ + ctx->json5 = (char*)src->data; + ctx->depth = 0; + ctx->tokensSize = r.num_tokens; + ctx->index = 0; + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_decodeJson(const UA_ByteString *src, void *dst, const UA_DataType *type, + const UA_DecodeJsonOptions *options) { + if(!dst || !src || !type) + return UA_STATUSCODE_BADARGUMENTSMISSING; + + /* Set up the context */ + cj5_token tokens[UA_JSON_MAXTOKENCOUNT]; + ParseCtx ctx; + memset(&ctx, 0, sizeof(ParseCtx)); + ctx.tokens = tokens; + + if(options) { + ctx.namespaceMapping = options->namespaceMapping; + ctx.serverUris = options->serverUris; + ctx.serverUrisSize = options->serverUrisSize; + ctx.customTypes = options->customTypes; + } + + /* Decode */ + status ret = tokenize(&ctx, src, UA_JSON_MAXTOKENCOUNT, + options ? options->decodedLength : NULL); + if(ret != UA_STATUSCODE_GOOD) + goto cleanup; + + memset(dst, 0, type->memSize); /* Initialize the value */ + ret = decodeJsonJumpTable[type->typeKind](&ctx, dst, type); + + /* Sanity check if all tokens were processed */ + if(ctx.index != ctx.tokensSize && + ctx.index != ctx.tokensSize - 1) + ret = UA_STATUSCODE_BADDECODINGERROR; + + cleanup: + + /* Free token array on the heap */ + if(ctx.tokens != tokens) + UA_free((void*)(uintptr_t)ctx.tokens); + + if(ret != UA_STATUSCODE_GOOD) + UA_clear(dst, type); + return ret; +} + +#endif /* UA_ENABLE_JSON_ENCODING_LEGACY */ diff --git a/src/ua_types_encoding_json_105.c b/src/ua_types_encoding_json_105.c index 00c2203ea..7ca84e3c5 100644 --- a/src/ua_types_encoding_json_105.c +++ b/src/ua_types_encoding_json_105.c @@ -6,10 +6,17 @@ * Copyright 2018 (c) Fraunhofer IOSB (Author: Lukas Meling) */ +/** + * This file contains the JSON encoding/decoding following the v1.05 OPC UA + * specification. The changes in the v1.05 specification are breaking. The + * encoding is not compatible with previous versions. Enable + * UA_ENABLE_JSON_ENCODING_LEGACY to use the old JSON encoding instead. + */ + #include #include -#ifdef UA_ENABLE_JSON_ENCODING +#if defined(UA_ENABLE_JSON_ENCODING) && !defined(UA_ENABLE_JSON_ENCODING_LEGACY) #include "ua_types_encoding_json.h" @@ -2570,4 +2577,4 @@ UA_decodeJson(const UA_ByteString *src, void *dst, const UA_DataType *type, return ret; } -#endif /* UA_ENABLE_JSON_ENCODING */ +#endif /* defined(UA_ENABLE_JSON_ENCODING) && !defined(UA_ENABLE_JSON_ENCODING_LEGACY) */ From 43aa78b6c5ede99a7cc9aab7f97ac97ec3643976 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 1 Jan 2025 14:04:25 +0100 Subject: [PATCH 111/158] fix(core): Null variants get encoded as an empty object --- src/ua_types_encoding_json_105.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ua_types_encoding_json_105.c b/src/ua_types_encoding_json_105.c index 7ca84e3c5..ce114292d 100644 --- a/src/ua_types_encoding_json_105.c +++ b/src/ua_types_encoding_json_105.c @@ -794,7 +794,7 @@ encodeVariantInner(CtxJson *ctx, const UA_Variant *src) { * JSON object shall be omitted or replaced by the JSON literal ‘null’ (when * an element of a JSON array). */ if(!src->type) - return writeJsonObjStart(ctx) | writeJsonObjEnd(ctx); + return UA_STATUSCODE_GOOD; /* Set the array type in the encoding mask */ const bool isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL; @@ -1856,6 +1856,12 @@ Array_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void **dst, const UA_DataTy static status decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { + /* Empty variant == null */ + if(ctx->tokens[ctx->index].size == 0) { + ctx->index++; + return UA_STATUSCODE_GOOD; + } + /* Search for the type */ size_t typeIndex = 0; status ret = lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); From b930774b0c1912460245ac66efe337a133bbbbbe Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 1 Jan 2025 22:12:23 +0100 Subject: [PATCH 112/158] refactor(deps): Reduce the table size for the base64 decoding --- deps/base64.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/deps/base64.c b/deps/base64.c index d2b9676f8..a26d3dbe0 100644 --- a/deps/base64.c +++ b/deps/base64.c @@ -59,7 +59,7 @@ UA_base64_buf(const unsigned char *src, size_t len, unsigned char *out) { return (size_t)(pos - out); } -static unsigned char dtable[256] = { +static unsigned char dtable[128] = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 62 , 0x80, 62 , 0x80, 63 , @@ -67,15 +67,7 @@ static unsigned char dtable[256] = { 0x80, 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 0x80, 0x80, 0x80, 0x80, 63 , 0x80, 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , - 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 + 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 0x80, 0x80, 0x80, 0x80, 0x80 }; unsigned char * @@ -102,7 +94,7 @@ UA_unbase64(const unsigned char *src, size_t len, size_t *out_len) { unsigned char block[4]; unsigned char *pos = out; for(size_t i = 0; i < len; i++) { - unsigned char tmp = dtable[src[i]]; + unsigned char tmp = dtable[src[i] & 0x07f]; if(tmp == 0x80) goto error; /* Invalid input */ From ac3b6f4ed76df1ada34bbe14b11eae121e5df849 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 1 Jan 2025 22:13:03 +0100 Subject: [PATCH 113/158] fix(core): Base64 can contain the '/' character, so b= NodeIds need to be unescaped --- src/util/ua_types_lex.c | 26 +++++++++++++++++++++++--- src/util/ua_types_lex.re | 26 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/util/ua_types_lex.c b/src/util/ua_types_lex.c index 21c5ef367..da62b7b67 100644 --- a/src/util/ua_types_lex.c +++ b/src/util/ua_types_lex.c @@ -127,14 +127,34 @@ parse_nodeid_body(UA_NodeId *id, const char *body, const char *end, Escaping esc if(res == UA_STATUSCODE_GOOD) id->identifierType = UA_NODEIDTYPE_GUID; break; - case 'b': + case 'b': { + /* A base64 string may contain the / char which can get and-escaped. */ + UA_String tmp = {len, (UA_Byte*)(uintptr_t)body + 2}; + UA_String escaped = tmp; + for(const char *pos = body + 2; pos < end; pos++) { + if(*pos == '&') { + res = UA_String_copy(&tmp, &escaped); + if(res != UA_STATUSCODE_GOOD) + return res; + char *begin = (char*)escaped.data; + char *esc_end = unescape(begin, begin + escaped.length); + if(esc_end > begin) + escaped.length = (size_t)(esc_end - begin); + else + UA_String_clear(&escaped); + break; + } + } id->identifier.byteString.data = - UA_unbase64((const unsigned char*)body+2, len, + UA_unbase64((const unsigned char*)escaped.data, escaped.length, &id->identifier.byteString.length); - if(!id->identifier.byteString.data && len > 0) + if(escaped.data != (const UA_Byte*)body + 2) + UA_String_clear(&escaped); + if(!id->identifier.byteString.data && escaped.length > 0) return UA_STATUSCODE_BADDECODINGERROR; id->identifierType = UA_NODEIDTYPE_BYTESTRING; break; + } default: return UA_STATUSCODE_BADDECODINGERROR; } diff --git a/src/util/ua_types_lex.re b/src/util/ua_types_lex.re index 324f1ca38..6615b113c 100644 --- a/src/util/ua_types_lex.re +++ b/src/util/ua_types_lex.re @@ -135,14 +135,34 @@ parse_nodeid_body(UA_NodeId *id, const char *body, const char *end, Escaping esc if(res == UA_STATUSCODE_GOOD) id->identifierType = UA_NODEIDTYPE_GUID; break; - case 'b': + case 'b': { + /* A base64 string may contain the / char which can get and-escaped. */ + UA_String tmp = {len, (UA_Byte*)(uintptr_t)body + 2}; + UA_String escaped = tmp; + for(const char *pos = body + 2; pos < end; pos++) { + if(*pos == '&') { + res = UA_String_copy(&tmp, &escaped); + if(res != UA_STATUSCODE_GOOD) + return res; + char *begin = (char*)escaped.data; + char *esc_end = unescape(begin, begin + escaped.length); + if(esc_end > begin) + escaped.length = (size_t)(esc_end - begin); + else + UA_String_clear(&escaped); + break; + } + } id->identifier.byteString.data = - UA_unbase64((const unsigned char*)body+2, len, + UA_unbase64((const unsigned char*)escaped.data, escaped.length, &id->identifier.byteString.length); - if(!id->identifier.byteString.data && len > 0) + if(escaped.data != (const UA_Byte*)body + 2) + UA_String_clear(&escaped); + if(!id->identifier.byteString.data && escaped.length > 0) return UA_STATUSCODE_BADDECODINGERROR; id->identifierType = UA_NODEIDTYPE_BYTESTRING; break; + } default: return UA_STATUSCODE_BADDECODINGERROR; } From 8414614764632069c10a7200737a0c65ab23ec2b Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 2 Jan 2025 11:52:19 +0100 Subject: [PATCH 114/158] fix(core): Ensure that decode->encode->decode yields the identical result --- src/ua_types_encoding_json_105.c | 66 ++++++++++----------------- tests/fuzz/fuzz_json_decode_encode.cc | 5 +- 2 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/ua_types_encoding_json_105.c b/src/ua_types_encoding_json_105.c index ce114292d..da6c5d5b6 100644 --- a/src/ua_types_encoding_json_105.c +++ b/src/ua_types_encoding_json_105.c @@ -1498,7 +1498,7 @@ DECODE_JSON(QualifiedName) { return UA_QualifiedName_parseEx(dst, str, ctx->namespaceMapping); } -UA_FUNC_ATTR_WARN_UNUSED_RESULT status +status lookAheadForKey(ParseCtx *ctx, const char *key, size_t *resultIndex) { /* The current index must point to the beginning of an object. * This has to be ensured by the caller. */ @@ -1862,14 +1862,18 @@ decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { return UA_STATUSCODE_GOOD; } - /* Search for the type */ - size_t typeIndex = 0; - status ret = lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); - if(ret != UA_STATUSCODE_GOOD) - return UA_STATUSCODE_BADDECODINGERROR; + /* Search the value field */ + size_t valueIndex = 0; + lookAheadForKey(ctx, UA_JSONKEY_VALUE, &valueIndex); + + /* Search for the dimensions field */ + size_t dimIndex = 0; + lookAheadForKey(ctx, UA_JSONKEY_DIMENSIONS, &dimIndex); /* Parse the type kind */ - if(ctx->tokens[typeIndex].type != CJ5_TOKEN_NUMBER) + size_t typeIndex = 0; + lookAheadForKey(ctx, UA_JSONKEY_TYPE, &typeIndex); + if(typeIndex == 0 || ctx->tokens[typeIndex].type != CJ5_TOKEN_NUMBER) return UA_STATUSCODE_BADDECODINGERROR; UA_UInt64 typeKind = 0; size_t len = parseUInt64(&ctx->json5[ctx->tokens[typeIndex].start], @@ -1877,48 +1881,25 @@ decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { if(len == 0) return UA_STATUSCODE_BADDECODINGERROR; - /* Get the datatype of the content. The type must be a builtin data type. + /* Shift to get the datatype index. The type must be a builtin data type. * All not-builtin types are wrapped in an ExtensionObject. */ typeKind--; if(typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO) return UA_STATUSCODE_BADDECODINGERROR; const UA_DataType *type = &UA_TYPES[typeKind]; - /* Search for the dimensions */ - size_t *dimPtr = NULL; - size_t dimIndex = 0; - ret = lookAheadForKey(ctx, UA_JSONKEY_DIMENSIONS, &dimIndex); - if(ret == UA_STATUSCODE_GOOD && ctx->tokens[dimIndex].size > 0) - dimPtr = &dimIndex; - - /* Search the value field */ - size_t valueIndex = 0; - size_t beginIndex = ctx->index; - ret = lookAheadForKey(ctx, UA_JSONKEY_VALUE, &valueIndex); - if(ret != UA_STATUSCODE_GOOD || - ctx->tokens[valueIndex].type == CJ5_TOKEN_NULL) { - /* Scalar with dimensions -> error */ - if(dimPtr) - return UA_STATUSCODE_BADDECODINGERROR; - /* Null value */ - dst->data = UA_new(type); - if(!dst->data) - return UA_STATUSCODE_BADOUTOFMEMORY; - dst->type = type; - skipObject(ctx); - return UA_STATUSCODE_GOOD; - } - /* Value is an array? */ - UA_Boolean isArray = (ctx->tokens[valueIndex].type == CJ5_TOKEN_ARRAY); + UA_Boolean isArray = + (valueIndex > 0 && ctx->tokens[valueIndex].type == CJ5_TOKEN_ARRAY); /* Decode the value */ - ctx->depth++; - ctx->index = valueIndex; status res = UA_STATUSCODE_GOOD; + size_t beginIndex = ctx->index; + ctx->index = valueIndex; + ctx->depth++; if(!isArray) { /* Scalar with dimensions -> error */ - if(dimPtr) { + if(dimIndex > 0) { res = UA_STATUSCODE_BADDECODINGERROR; goto out; } @@ -1931,7 +1912,7 @@ decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { } /* Decode a value wrapped in an ExtensionObject */ - if(type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { + if(valueIndex > 0 && type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { res = Variant_decodeJsonUnwrapExtensionObject(ctx, dst, NULL); goto out; } @@ -1942,10 +1923,11 @@ decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { res = UA_STATUSCODE_BADOUTOFMEMORY; goto out; } + dst->type = type; /* Decode the value */ - dst->type = type; - res = decodeJsonJumpTable[type->typeKind](ctx, dst->data, type); + if(valueIndex > 0 && ctx->tokens[valueIndex].type != CJ5_TOKEN_NULL) + res = decodeJsonJumpTable[type->typeKind](ctx, dst->data, type); } else { /* Decode an array. Try to unwrap ExtensionObjects in the array. The * members must all have the same type. */ @@ -1961,8 +1943,8 @@ decodeJSONVariant(ParseCtx *ctx, UA_Variant *dst) { } /* Decode array dimensions */ - if(dimPtr) { - ctx->index = *dimPtr; + if(dimIndex > 0) { + ctx->index = dimIndex; res |= Array_decodeJson(ctx, (void**)&dst->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]); /* Validate the dimensions */ diff --git a/tests/fuzz/fuzz_json_decode_encode.cc b/tests/fuzz/fuzz_json_decode_encode.cc index 0be8e19b0..c9f7de613 100644 --- a/tests/fuzz/fuzz_json_decode_encode.cc +++ b/tests/fuzz/fuzz_json_decode_encode.cc @@ -49,9 +49,8 @@ LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { return 0; } UA_assert(retval == UA_STATUSCODE_GOOD); - /* TODO: Enable this assertion when the binary-JSON-binary roundtrip is complete. - * Waiting for Mantis issue #7750. - * UA_assert(UA_order(&value, &value2, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ); */ + + UA_assert(UA_order(&value, &value2, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ); UA_ByteString buf3 = UA_BYTESTRING_NULL; retval = UA_ByteString_allocBuffer(&buf3, jsonSize); From 7d8da03e9fd8afc5017f02d9bf324a3cd2997685 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 2 Jan 2025 20:51:55 +0100 Subject: [PATCH 115/158] fix(core): Decode first as regular JSON string (for QualifiedName, NodeId, ExpandedNodeId) That way \uXXXX string esacping is possible. --- src/ua_types_encoding_json_105.c | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ua_types_encoding_json_105.c b/src/ua_types_encoding_json_105.c index da6c5d5b6..077eaf69e 100644 --- a/src/ua_types_encoding_json_105.c +++ b/src/ua_types_encoding_json_105.c @@ -1489,13 +1489,13 @@ DECODE_JSON(LocalizedText) { } DECODE_JSON(QualifiedName) { - CHECK_TOKEN_BOUNDS; - CHECK_STRING; - GET_TOKEN; - - ctx->index++; - UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; - return UA_QualifiedName_parseEx(dst, str, ctx->namespaceMapping); + UA_String str; + UA_String_init(&str); + status res = String_decodeJson(ctx, &str, NULL); + if(res == UA_STATUSCODE_GOOD) + res = UA_QualifiedName_parseEx(dst, str, ctx->namespaceMapping); + UA_String_clear(&str); + return res; } status @@ -1533,24 +1533,24 @@ lookAheadForKey(ParseCtx *ctx, const char *key, size_t *resultIndex) { } DECODE_JSON(NodeId) { - CHECK_TOKEN_BOUNDS; - CHECK_STRING; - GET_TOKEN; - - ctx->index++; - UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; - return UA_NodeId_parseEx(dst, str, ctx->namespaceMapping); + UA_String str; + UA_String_init(&str); + status res = String_decodeJson(ctx, &str, NULL); + if(res == UA_STATUSCODE_GOOD) + res = UA_NodeId_parseEx(dst, str, ctx->namespaceMapping); + UA_String_clear(&str); + return res; } DECODE_JSON(ExpandedNodeId) { - CHECK_TOKEN_BOUNDS; - CHECK_STRING; - GET_TOKEN; - - ctx->index++; - UA_String str = {tokenSize, (UA_Byte*)(uintptr_t)tokenData}; - return UA_ExpandedNodeId_parseEx(dst, str, ctx->namespaceMapping, - ctx->serverUrisSize, ctx->serverUris); + UA_String str; + UA_String_init(&str); + status res = String_decodeJson(ctx, &str, NULL); + if(res == UA_STATUSCODE_GOOD) + res = UA_ExpandedNodeId_parseEx(dst, str, ctx->namespaceMapping, + ctx->serverUrisSize, ctx->serverUris); + UA_String_clear(&str); + return res; } DECODE_JSON(DateTime) { From 0f945f0e4fa7208c86b3f7dd9ac3985e7de8ef79 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Thu, 2 Jan 2025 20:56:08 +0100 Subject: [PATCH 116/158] refactor(core): Simplify JSON StatusCode decoding --- src/ua_types_encoding_json_105.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ua_types_encoding_json_105.c b/src/ua_types_encoding_json_105.c index 077eaf69e..3759b4871 100644 --- a/src/ua_types_encoding_json_105.c +++ b/src/ua_types_encoding_json_105.c @@ -1686,19 +1686,12 @@ DECODE_JSON(DateTime) { return UA_STATUSCODE_GOOD; } -static UA_StatusCode -decodeJsonNop(ParseCtx *ctx, void *dst, const UA_DataType *type) { - return UA_STATUSCODE_GOOD; -} - DECODE_JSON(StatusCode) { CHECK_OBJECT; - DecodeEntry entries[2] = { {UA_JSONKEY_CODE, dst, NULL, false, &UA_TYPES[UA_TYPES_UINT32]}, - {UA_JSONKEY_SYMBOL, NULL, decodeJsonNop, false, &UA_TYPES[UA_TYPES_STRING]} + {UA_JSONKEY_SYMBOL, NULL, NULL, false, NULL} }; - return decodeFields(ctx, entries, 2); } From b69f612e26fcd9eeb478de256078440c55513077 Mon Sep 17 00:00:00 2001 From: sbreuss Date: Wed, 8 Jan 2025 11:40:50 +0100 Subject: [PATCH 117/158] fix(build): Set git version number only when open62541 is the main CMake project This commit disables reading the version number from git when open62541 is not the main CMake project. This is necessary when including the project via CPM, as the version number would get overwritten by the git tag of the main project. --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07375ebfd..01e46298d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,9 +48,11 @@ set(OPEN62541_VER_PATCH 8) set(OPEN62541_VER_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty" set(OPEN62541_VER_COMMIT "unknown-commit") -# Overwrite the version information based on git if available -include(SetGitBasedVersion) -set_open62541_version() +# Overwrite the version information based on git if available and we are the main cmake project. +if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + include(SetGitBasedVersion) + set_open62541_version() +endif() # Examples for the version string are: # v1.2 From a75b503212e06f62de806b3dc1c8e9aaf66c6815 Mon Sep 17 00:00:00 2001 From: Marwin Glaser Date: Tue, 17 Dec 2024 16:33:32 +0100 Subject: [PATCH 118/158] fix(ci): make codeql workflow run for all 1.* release branches --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4ddf6c1bc..188db3190 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,9 +2,9 @@ name: "Code Scanning" on: push: - branches: [ master, 1\.1, 1\.2 ] + branches: [ master, 1.* ] pull_request: - branches: [ master, 1\.1, 1\.2 ] + branches: [ master, 1.* ] paths-ignore: - '**/*.md' - '**/*.txt' From e0415b4f2d397c98c774632175ff395b65ef8905 Mon Sep 17 00:00:00 2001 From: Marwin Glaser Date: Tue, 17 Dec 2024 16:35:34 +0100 Subject: [PATCH 119/158] fix(ci): remove unavailable filter on.pull_request.tags --- .github/workflows/coverage.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0c851f5d4..7a10b18af 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,8 +6,6 @@ on: - v1.* pull_request: branches: [ main, master ] - tags: - - v1.* jobs: run: From 8934cab5f5894fac6ff7737a1c9d6218c69525a6 Mon Sep 17 00:00:00 2001 From: Marwin Glaser Date: Tue, 17 Dec 2024 16:40:07 +0100 Subject: [PATCH 120/158] refactor(ci): replace use of deprecated set-output workflow command --- .github/workflows/doc_upload.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc_upload.yml b/.github/workflows/doc_upload.yml index 11fc5d177..4b7de38be 100644 --- a/.github/workflows/doc_upload.yml +++ b/.github/workflows/doc_upload.yml @@ -33,7 +33,7 @@ jobs: persist-credentials: false - name: Get the current branch name shell: bash - run: echo "::set-output name=branch::${GITHUB_REF##*/}" + run: echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" id: myref - name: Copy Documentation Files to Website repository run: | From 2076a9c87fc7e46590d3709f3062791686f69adb Mon Sep 17 00:00:00 2001 From: tomitkl Date: Fri, 10 Jan 2025 15:28:37 +0200 Subject: [PATCH 121/158] feat(plugin): Add filestore Windows implementation (#7006) * feat(plugin): Add filestore Windows implementation * Define UA_DT_REG and UA_DT_DIR * Remove build flag and use __linux__ or UA_ARCHITECTURE_WIN32 in places which used to check only for __linux__ --- CMakeLists.txt | 7 +- deps/README.md | 1 + deps/dirent.h | 1239 +++++++++++++++++ .../crypto/ua_certificategroup_filestore.c | 139 +- plugins/crypto/ua_filestore_common.c | 38 +- plugins/crypto/ua_filestore_common.h | 92 +- plugins/crypto/ua_securitypolicy_filestore.c | 53 +- .../plugin/certificategroup_default.h | 6 +- .../open62541/plugin/securitypolicy_default.h | 6 +- .../include/open62541/server_config_default.h | 12 +- plugins/ua_config_default.c | 6 +- plugins/ua_config_json.c | 2 +- tests/encryption/check_certificategroup.c | 31 +- .../check_encryption_aes128sha256rsaoaep.c | 8 +- .../check_encryption_aes256sha256rsapss.c | 8 +- .../check_encryption_basic128rsa15.c | 8 +- tests/encryption/check_encryption_basic256.c | 8 +- .../check_encryption_basic256sha256.c | 8 +- .../encryption/check_encryption_eccnistp256.c | 8 +- tests/encryption/check_update_certificate.c | 24 +- tests/encryption/check_update_trustlist.c | 41 +- tests/testing-plugins/test_helpers.h | 4 +- 22 files changed, 1560 insertions(+), 189 deletions(-) create mode 100644 deps/dirent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e46298d..454cc63db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,6 @@ else() endif() endif() - # security provider set(UA_ENCRYPTION_PLUGINS "MBEDTLS" "OPENSSL" "LIBRESSL") set(UA_ENABLE_ENCRYPTION OFF CACHE STRING "Encryption support (LibreSSL EXPERIMENTAL)") @@ -810,6 +809,10 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_internal.h ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h) +if(UA_ENABLE_ENCRYPTION AND UA_ARCHITECTURE_WIN32) + list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/deps/dirent.h) +endif() + set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.c ${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.c @@ -1024,7 +1027,7 @@ endif() # Always include encryption plugins into the amalgamation # Use guards in the files to ensure that UA_ENABLE_ENCRYPTON_MBEDTLS and UA_ENABLE_ENCRYPTION_OPENSSL are honored. -if((UNIX AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION) +if(((UNIX OR UA_ARCHITECTURE_WIN32) AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION) list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.h ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.c ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c diff --git a/deps/README.md b/deps/README.md index 95232b8f6..b2c71067a 100644 --- a/deps/README.md +++ b/deps/README.md @@ -22,3 +22,4 @@ The following third party libraries may be included -- depending on the activate | dtoa | BSL (Boost) | Printing of float numbers | | mp_printf | MIT | Our version of github:mpaland/printf | | utf8 | MPL 2.0 | Lightweight utf8 de/encoding | +| dirent | MIT | Dirent interface for Microsoft Visual Studio | diff --git a/deps/dirent.h b/deps/dirent.h new file mode 100644 index 000000000..fd1e81354 --- /dev/null +++ b/deps/dirent.h @@ -0,0 +1,1239 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* + * Symbolic link. Be ware that S_IFLNK value and S_ISLNK() macro are only + * usable with dirent - they do not work with stat() function call! + */ +#if !defined(S_IFLNK) +# define S_IFLNK (_S_IFDIR | _S_IFREG) +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* User full permissions */ +#if !defined(S_IRWXU) +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Group full permissions */ +#if !defined(S_IRWXG) +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Other full permissions */ +#if !defined(S_IRWXO) +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices and sockets cannot be + * distinguished on Windows, and the macros S_ISBLK and S_ISSOCK are only + * defined for compatibility. These macros should always return false on + * Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* Position of next file in a directory stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* True if next entry is invalid */ + int invalid; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* Position of next file in a directory stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir(const char *dirname); +static _WDIR *_wopendir(const wchar_t *dirname); + +static struct dirent *readdir(DIR *dirp); +static struct _wdirent *_wreaddir(_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir(DIR *dirp); +static int _wclosedir(_WDIR *dirp); + +static void rewinddir(DIR *dirp); +static void _wrewinddir(_WDIR *dirp); + +static long telldir(DIR *dirp); +static long _wtelldir(_WDIR *dirp); + +static void seekdir(DIR *dirp, long loc); +static void _wseekdir(_WDIR *dirp, long loc); + +static int scandir(const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort(const struct dirent **a, const struct dirent **b); + +static int versionsort(const struct dirent **a, const struct dirent **b); + +static int strverscmp(const char *a, const char *b); + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir +#define wtelldir _wtelldir +#define wseekdir _wseekdir + +/* Compatibility with older Microsoft compilers and non-Microsoft compilers */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +# define wcstombs_s dirent_wcstombs_s +# define mbstowcs_s dirent_mbstowcs_s +#endif + +/* Optimize dirent_set_errno() away on modern Microsoft compilers */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define dirent_set_errno _set_errno +#endif + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp); +static long dirent_hash(WIN32_FIND_DATAW *datap); + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, size_t sizeInWords, + const char *mbstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, size_t sizeInBytes, + const wchar_t *wcstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void dirent_set_errno(int error); +#endif + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR * +_wopendir(const wchar_t *dirname) +{ + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + _WDIR *dirp = (_WDIR*) malloc(sizeof(struct _WDIR)); + if (!dirp) + return NULL; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + dirp->invalid = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL); +#else + /* WinRT */ + size_t n = wcslen(dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc(sizeof(wchar_t) * n + 16); + if (dirp->patt == NULL) + goto exit_closedir; + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW(dirname, n, dirp->patt, NULL); + if (n <= 0) + goto exit_closedir; +#else + /* WinRT */ + wcsncpy_s(dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first(dirp)) + goto exit_closedir; + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir(dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent * +_wreaddir(_WDIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct _wdirent *entry; + (void) _wreaddir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) +{ + /* Validate directory handle */ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE || !dirp->patt) { + dirent_set_errno(EBADF); + *result = NULL; + return -1; + } + + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* Return NULL to indicate end of directory */ + *result = NULL; + return /*OK*/0; + } + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + size_t i = 0; + while (i < PATH_MAX && datap->cFileName[i] != 0) { + entry->d_name[i] = datap->cFileName[i]; + i++; + } + entry->d_name[i] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = i; + + /* Determine file type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) + entry->d_type = DT_LNK; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Read the next directory entry to cache */ + datap = dirent_next(dirp); + if (datap) { + /* Compute 31-bit hash of the next directory entry */ + entry->d_off = dirent_hash(datap); + + /* Push the next directory entry back to cache */ + dirp->cached = 1; + } else { + /* End of directory stream */ + entry->d_off = (long) ((~0UL) >> 1); + } + + /* Reset other fields */ + entry->d_ino = 0; + entry->d_reclen = sizeof(struct _wdirent); + + /* Set result address */ + *result = entry; + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir(_WDIR *dirp) +{ + if (!dirp) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* + * Release search handle if we have one. Being able to handle + * partially initialized _WDIR structure allows us to use this + * function to handle errors occuring within _wopendir. + */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose(dirp->handle); + } + + /* + * Release search pattern. Note that we don't need to care if + * dirp->patt is NULL or not: function free is guaranteed to act + * appropriately. + */ + free(dirp->patt); + + /* Release directory structure */ + free(dirp); + return /*success*/0; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void _wrewinddir(_WDIR* dirp) +{ + /* Check directory pointer */ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE || !dirp->patt) + return; + + /* Release existing search handle */ + FindClose(dirp->handle); + + /* Open new search handle */ + dirent_first(dirp); +} + +/* Get first directory entry */ +static WIN32_FIND_DATAW * +dirent_first(_WDIR *dirp) +{ + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle == INVALID_HANDLE_VALUE) + goto error; + + /* A directory entry is now waiting in memory */ + dirp->cached = 1; + return &dirp->data; + +error: + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + dirp->invalid = 1; + + /* Set error code */ + DWORD errorcode = GetLastError(); + switch (errorcode) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno(EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno(ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno(ENOENT); + } + return NULL; +} + +/* Get next directory entry */ +static WIN32_FIND_DATAW * +dirent_next(_WDIR *dirp) +{ + /* Return NULL if seek position was invalid */ + if (dirp->invalid) + return NULL; + + /* Is the next directory entry already in cache? */ + if (dirp->cached) { + /* Yes, a valid directory entry found in memory */ + dirp->cached = 0; + return &dirp->data; + } + + /* Read the next directory entry from stream */ + if (FindNextFileW(dirp->handle, &dirp->data) == FALSE) { + /* End of directory stream */ + return NULL; + } + + /* Success */ + return &dirp->data; +} + +/* + * Compute 31-bit hash of file name. + * + * See djb2 at http://www.cse.yorku.ca/~oz/hash.html + */ +static long +dirent_hash(WIN32_FIND_DATAW *datap) +{ + unsigned long hash = 5381; + unsigned long c; + const wchar_t *p = datap->cFileName; + const wchar_t *e = p + MAX_PATH; + while (p != e && (c = *p++) != 0) { + hash = (hash << 5) + hash + c; + } + + return (long) (hash & ((~0UL) >> 1)); +} + +/* Open directory stream using plain old C-string */ +static DIR *opendir(const char *dirname) +{ + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + struct DIR *dirp = (DIR*) malloc(sizeof(struct DIR)); + if (!dirp) + return NULL; + + /* Convert directory name to wide-character string */ + wchar_t wname[PATH_MAX + 1]; + size_t n; + int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX+1); + if (error) + goto exit_failure; + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir(wname); + if (!dirp->wdirp) + goto exit_failure; + + /* Success */ + return dirp; + + /* Failure */ +exit_failure: + free(dirp); + return NULL; +} + +/* Read next directory entry */ +static struct dirent * +readdir(DIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct dirent *entry; + (void) readdir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result) +{ + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp->wdirp); + if (!datap) { + /* No more directory entries */ + *result = NULL; + return /*OK*/0; + } + + /* Attempt to convert file name to multi-byte string */ + size_t n; + int error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, then + * attempt to use old 8+3 file name. This allows the program to + * access files although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file name + * unless the file system provides one. At least VirtualBox shared + * folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* Determine file type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) + entry->d_type = DT_LNK; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Get offset of next file */ + datap = dirent_next(dirp->wdirp); + if (datap) { + /* Compute 31-bit hash of the next directory entry */ + entry->d_off = dirent_hash(datap); + + /* Push the next directory entry back to cache */ + dirp->wdirp->cached = 1; + } else { + /* End of directory stream */ + entry->d_off = (long) ((~0UL) >> 1); + } + + /* Reset fields */ + entry->d_ino = 0; + entry->d_reclen = sizeof(struct dirent); + } else { + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + } + + /* Return pointer to directory entry */ + *result = entry; + return /*OK*/0; +} + +/* Close directory stream */ +static int +closedir(DIR *dirp) +{ + int ok; + + if (!dirp) + goto exit_failure; + + /* Close wide-character directory stream */ + ok = _wclosedir(dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free(dirp); + return ok; + +exit_failure: + /* Invalid directory stream */ + dirent_set_errno(EBADF); + return /*failure*/-1; +} + +/* Rewind directory stream to beginning */ +static void +rewinddir(DIR *dirp) +{ + if (!dirp) + return; + + /* Rewind wide-character string directory stream */ + _wrewinddir(dirp->wdirp); +} + +/* Get position of directory stream */ +static long +_wtelldir(_WDIR *dirp) +{ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* Read next file entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* End of directory stream */ + return (long) ((~0UL) >> 1); + } + + /* Store file entry to cache for readdir() */ + dirp->cached = 1; + + /* Return the 31-bit hash code to be used as stream position */ + return dirent_hash(datap); +} + +/* Get position of directory stream */ +static long +telldir(DIR *dirp) +{ + if (!dirp) { + dirent_set_errno(EBADF); + return -1; + } + + return _wtelldir(dirp->wdirp); +} + +/* Seek directory stream to offset */ +static void +_wseekdir(_WDIR *dirp, long loc) +{ + if (!dirp) + return; + + /* Directory must be open */ + if (dirp->handle == INVALID_HANDLE_VALUE) + goto exit_failure; + + /* Ensure that seek position is valid */ + if (loc < 0) + goto exit_failure; + + /* Restart directory stream from the beginning */ + FindClose(dirp->handle); + if (!dirent_first(dirp)) + goto exit_failure; + + /* Reset invalid flag so that we can read from the stream again */ + dirp->invalid = 0; + + /* + * Read directory entries from the beginning until the hash matches a + * file name. Be ware that hash code is only 31 bits longs and + * duplicates are possible: the hash code cannot return the position + * with 100.00% accuracy! Moreover, the method is slow for large + * directories. + */ + long hash; + do { + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* + * End of directory stream was reached before finding + * the requested location. Perhaps the file in + * question was deleted or moved out of the directory. + */ + goto exit_failure; + } + + /* Does the file name match the hash? */ + hash = dirent_hash(datap); + } while (hash != loc); + + /* + * File name matches the hash! Push the directory entry back to cache + * from where next readdir() will return it. + */ + dirp->cached = 1; + dirp->invalid = 0; + return; + +exit_failure: + /* Ensure that readdir will return NULL */ + dirp->invalid = 1; +} + +/* Seek directory stream to offset */ +static void +seekdir(DIR *dirp, long loc) +{ + if (!dirp) + return; + + _wseekdir(dirp->wdirp, loc); +} + +/* Scan directory for entries */ +static int +scandir( + const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + int result; + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Cannot open directory */ + return /*Error*/ -1; + } + + /* Read directory entries to memory */ + struct dirent *tmp = NULL; + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + while (1) { + /* Allocate room for a temporary directory entry */ + if (!tmp) { + tmp = (struct dirent*) malloc(sizeof(struct dirent)); + if (!tmp) + goto exit_failure; + } + + /* Read directory entry to temporary area */ + struct dirent *entry; + if (readdir_r(dir, tmp, &entry) != /*OK*/0) + goto exit_failure; + + /* Stop if we already read the last directory entry */ + if (entry == NULL) + goto exit_success; + + /* Determine whether to include the entry in results */ + if (filter && !filter(tmp)) + continue; + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + /* Compute number of entries in the new table */ + size_t num_entries = size * 2 + 16; + + /* Allocate new pointer table or enlarge existing */ + void *p = realloc(files, sizeof(void*) * num_entries); + if (!p) + goto exit_failure; + + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } + + /* Store the temporary entry to ptr table */ + files[size++] = tmp; + tmp = NULL; + } + +exit_failure: + /* Release allocated entries */ + for (size_t i = 0; i < size; i++) { + free(files[i]); + } + + /* Release the pointer table */ + free(files); + files = NULL; + + /* Exit with error code */ + result = /*error*/ -1; + goto exit_status; + +exit_success: + /* Sort directory entries */ + if (size > 1 && compare) { + qsort(files, size, sizeof(void*), + (int (*) (const void*, const void*)) compare); + } + + /* Pass pointer table to caller */ + if (namelist) + *namelist = files; + + /* Return the number of directory entries read */ + result = (int) size; + +exit_status: + /* Release temporary directory entry, if we had one */ + free(tmp); + + /* Close directory stream */ + closedir(dir); + return result; +} + +/* Alphabetical sorting */ +static int +alphasort(const struct dirent **a, const struct dirent **b) +{ + return strcoll((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort(const struct dirent **a, const struct dirent **b) +{ + return strverscmp((*a)->d_name, (*b)->d_name); +} + +/* Compare strings */ +static int +strverscmp(const char *a, const char *b) +{ + size_t i = 0; + size_t j; + + /* Find first difference */ + while (a[i] == b[i]) { + if (a[i] == '\0') { + /* No difference */ + return 0; + } + ++i; + } + + /* Count backwards and find the leftmost digit */ + j = i; + while (j > 0 && isdigit(a[j-1])) { + --j; + } + + /* Determine mode of comparison */ + if (a[j] == '0' || b[j] == '0') { + /* Find the next non-zero digit */ + while (a[j] == '0' && a[j] == b[j]) { + j++; + } + + /* String with more digits is smaller, e.g 002 < 01 */ + if (isdigit(a[j])) { + if (!isdigit(b[j])) { + return -1; + } + } else if (isdigit(b[j])) { + return 1; + } + } else if (isdigit(a[j]) && isdigit(b[j])) { + /* Numeric comparison */ + size_t k1 = j; + size_t k2 = j; + + /* Compute number of digits in each string */ + while (isdigit(a[k1])) { + k1++; + } + while (isdigit(b[k2])) { + k2++; + } + + /* Number with more digits is bigger, e.g 999 < 1000 */ + if (k1 < k2) + return -1; + else if (k1 > k2) + return 1; + } + + /* Alphabetical comparison */ + return (int) ((unsigned char) a[i]) - ((unsigned char) b[i]); +} + +/* Convert multi-byte string to wide character string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int +dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, + size_t sizeInWords, const char *mbstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = mbstowcs(wcstr, mbstr, sizeInWords); + if (wcstr && n >= count) + return /*error*/ 1; + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) + n = sizeInWords - 1; + wcstr[n] = 0; + } + + /* Length of multi-byte string with zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Convert wide-character string to multi-byte string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int +dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, + size_t sizeInBytes, const wchar_t *wcstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = wcstombs(mbstr, wcstr, sizeInBytes); + if (mbstr && n >= count) + return /*error*/1; + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Set errno variable */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void +dirent_set_errno(int error) +{ + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; +} +#endif + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/plugins/crypto/ua_certificategroup_filestore.c b/plugins/crypto/ua_certificategroup_filestore.c index f4aab07fd..160426baf 100644 --- a/plugins/crypto/ua_certificategroup_filestore.c +++ b/plugins/crypto/ua_certificategroup_filestore.c @@ -9,34 +9,26 @@ #include #include -#include #include "ua_filestore_common.h" #include "mp_printf.h" #ifdef UA_ENABLE_ENCRYPTION -#ifdef __linux__ /* Linux only so far */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) +#ifdef __linux__ #define EVENT_SIZE (sizeof(struct inotify_event)) #define BUF_LEN (1024 * ( EVENT_SIZE + 16 )) +#endif /* __linux__ */ typedef struct { /* Memory cert store as a base */ UA_CertificateGroup *store; +#ifdef __linux__ int inotifyFd; +#endif /* __linux__ */ UA_String trustedCertFolder; UA_String trustedCrlFolder; @@ -49,13 +41,13 @@ typedef struct { } FileCertStore; static int -mkpath(char *dir, mode_t mode) { - struct stat sb; +mkpath(char *dir, UA_MODE mode) { + struct UA_STAT sb; if(dir == NULL) return 1; - if(!stat(dir, &sb)) + if(!UA_stat(dir, &sb)) return 0; /* Directory already exist */ size_t len = strlen(dir) + 1; @@ -66,10 +58,10 @@ mkpath(char *dir, mode_t mode) { /* Before the actual target directory is created, the recursive call ensures * that all parent directories are created or already exist. */ - mkpath(dirname(tmp_dir), mode); + mkpath(UA_dirname(tmp_dir), mode); UA_free(tmp_dir); - return mkdir(dir, mode); + return UA_mkdir(dir, mode); } static UA_StatusCode @@ -81,22 +73,23 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) { return UA_STATUSCODE_BADINTERNALERROR; /* remove all regular files from directory */ - DIR *dir = opendir(path); + UA_DIR *dir = UA_opendir(path); if(!dir) return UA_STATUSCODE_BADINTERNALERROR; - struct dirent *dirent; - while((dirent = readdir(dir)) != NULL) { - if(dirent->d_type == DT_REG) { - char file_name[FILENAME_MAX]; - mp_snprintf(file_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name); - remove(file_name); + struct UA_DIRENT *dirent; + while((dirent = UA_readdir(dir)) != NULL) { + if(dirent->d_type == UA_DT_REG) { + char file_name[UA_FILENAME_MAX]; + mp_snprintf(file_name, UA_FILENAME_MAX, "%s/%s", path, + (char *)dirent->d_name); + UA_remove(file_name); } - if(dirent->d_type == DT_DIR && removeSubDirs == true) { + if(dirent->d_type == UA_DT_DIR && removeSubDirs == true) { char *directory = (char*)dirent->d_name; - char dir_name[FILENAME_MAX]; - mp_snprintf(dir_name, FILENAME_MAX, "%s/%s", path, (char*)dirent->d_name); + char dir_name[UA_FILENAME_MAX]; + mp_snprintf(dir_name, UA_FILENAME_MAX, "%s/%s", path, (char *)dirent->d_name); if(strlen(directory) == 1 && directory[0] == '.') continue; @@ -106,7 +99,7 @@ removeAllFilesFromDir(const char *const path, bool removeSubDirs) { retval = removeAllFilesFromDir(dir_name, removeSubDirs); } } - closedir(dir); + UA_closedir(dir); return retval; } @@ -153,7 +146,8 @@ getCertFileName(const char *path, const UA_ByteString *certificate, subName = subjectNameBuffer; } - if(snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName, thumbprintBuffer) < 0) + if(mp_snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, subName, + thumbprintBuffer) < 0) retval = UA_STATUSCODE_BADINTERNALERROR; UA_String_clear(&thumbprint); @@ -168,54 +162,54 @@ static UA_StatusCode readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) { UA_StatusCode retval = UA_STATUSCODE_GOOD; - char listPath[PATH_MAX] = {0}; - mp_snprintf(listPath, PATH_MAX, "%.*s", + char listPath[UA_PATH_MAX] = {0}; + mp_snprintf(listPath, UA_PATH_MAX, "%.*s", (int)path.length, (char*)path.data); /* Determine number of certificates */ size_t numCerts = 0; - DIR *dir = opendir(listPath); + UA_DIR *dir = UA_opendir(listPath); if(!dir) return UA_STATUSCODE_BADINTERNALERROR; - struct dirent *dirent; - while((dirent = readdir(dir)) != NULL) { - if(dirent->d_type != DT_REG) + struct UA_DIRENT *dirent; + while((dirent = UA_readdir(dir)) != NULL) { + if(dirent->d_type != UA_DT_REG) continue; numCerts++; } retval = UA_Array_resize((void **)list, listSize, numCerts, &UA_TYPES[UA_TYPES_BYTESTRING]); if(retval != UA_STATUSCODE_GOOD) { - closedir(dir); + UA_closedir(dir); return retval; } /* Read files from directory */ size_t numActCerts = 0; - rewinddir(dir); + UA_rewinddir(dir); - while((dirent = readdir(dir)) != NULL) { - if(dirent->d_type != DT_REG) + while((dirent = UA_readdir(dir)) != NULL) { + if(dirent->d_type != UA_DT_REG) continue; if(numActCerts < numCerts) { /* Create filename to load */ - char filename[PATH_MAX]; - if(mp_snprintf(filename, PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) { - closedir(dir); + char filename[UA_PATH_MAX]; + if(mp_snprintf(filename, UA_PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) { + UA_closedir(dir); return UA_STATUSCODE_BADINTERNALERROR; } /* Load data from file */ retval = readFileToByteString(filename, &((*list)[numActCerts])); if(retval != UA_STATUSCODE_GOOD) { - closedir(dir); + UA_closedir(dir); return retval; } } numActCerts++; } - closedir(dir); + UA_closedir(dir); return retval; } @@ -265,12 +259,17 @@ reloadTrustStore(UA_CertificateGroup *certGroup) { if(certGroup == NULL) return UA_STATUSCODE_BADINTERNALERROR; + #ifdef __linux__ FileCertStore *context = (FileCertStore *)certGroup->context; char buffer[BUF_LEN]; const int length = read(context->inotifyFd, buffer, BUF_LEN ); if(length == -1 && errno != EAGAIN) return UA_STATUSCODE_BADINTERNALERROR; +#else + /* TODO: Implement a way to check for changes in the pki folder */ + const int length = 0; +#endif /* __linux__ */ /* No events, which means no changes to the pki folder */ /* If the nonblocking read() found no events to read, then @@ -294,8 +293,8 @@ writeCertificates(UA_CertificateGroup *certGroup, const UA_ByteString *list, UA_StatusCode retval = UA_STATUSCODE_GOOD; for(size_t i = 0; i < listSize; i++) { /* Create filename to load */ - char filename[PATH_MAX]; - retval = getCertFileName(listPath, &list[i], filename, PATH_MAX); + char filename[UA_PATH_MAX]; + retval = getCertFileName(listPath, &list[i], filename, UA_PATH_MAX); if(retval != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADINTERNALERROR; @@ -317,8 +316,8 @@ writeTrustList(UA_CertificateGroup *certGroup, const UA_ByteString *list, if(listSize > 0 && list == NULL) return UA_STATUSCODE_BADINTERNALERROR; - char listPath[PATH_MAX] = {0}; - mp_snprintf(listPath, PATH_MAX, "%.*s", (int)path.length, (char*)path.data); + char listPath[UA_PATH_MAX] = {0}; + mp_snprintf(listPath, UA_PATH_MAX, "%.*s", (int)path.length, (char *)path.data); /* remove existing files in directory */ UA_StatusCode retval = removeAllFilesFromDir(listPath, false); if(retval != UA_STATUSCODE_GOOD) @@ -374,13 +373,13 @@ writeTrustStore(UA_CertificateGroup *certGroup, const UA_UInt32 trustListMask) { static UA_StatusCode FileCertStore_setupStorePath(char *directory, char *rootDirectory, size_t rootDirectorySize, UA_String *out) { - char path[PATH_MAX] = {0}; + char path[UA_PATH_MAX] = {0}; size_t pathSize = 0; - strncpy(path, rootDirectory, PATH_MAX); - pathSize = strnlen(path, PATH_MAX); + strncpy(path, rootDirectory, UA_PATH_MAX); + pathSize = strnlen(path, UA_PATH_MAX); - strncpy(&path[pathSize], directory, PATH_MAX - pathSize); + strncpy(&path[pathSize], directory, UA_PATH_MAX - pathSize); *out = UA_STRING_ALLOC(path); @@ -397,14 +396,14 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String if(!context) return UA_STATUSCODE_BADINTERNALERROR; - char rootDirectory[PATH_MAX] = {0}; + char rootDirectory[UA_PATH_MAX] = {0}; size_t rootDirectorySize = 0; - if(directory.length <= 0 || directory.length >= PATH_MAX) + if(directory.length <= 0 || directory.length >= UA_PATH_MAX) return UA_STATUSCODE_BADINTERNALERROR; memcpy(rootDirectory, directory.data, directory.length); - rootDirectorySize = strnlen(rootDirectory, PATH_MAX); + rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX); /* Add Certificate Group Id */ UA_NodeId applCertGroup = @@ -415,19 +414,19 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP); if(UA_NodeId_equal(&certGroup->certificateGroupId, &applCertGroup)) { - strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", PATH_MAX - rootDirectorySize); + strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", UA_PATH_MAX - rootDirectorySize); } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &httpCertGroup)) { - strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", PATH_MAX - rootDirectorySize); + strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", UA_PATH_MAX - rootDirectorySize); } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &userTokenCertGroup)) { - strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", PATH_MAX - rootDirectorySize); + strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", UA_PATH_MAX - rootDirectorySize); } else { UA_String nodeIdStr; UA_String_init(&nodeIdStr); UA_NodeId_print(&certGroup->certificateGroupId, &nodeIdStr); - strncpy(&rootDirectory[rootDirectorySize], (char*)nodeIdStr.data, PATH_MAX - rootDirectorySize); + strncpy(&rootDirectory[rootDirectorySize], (char *)nodeIdStr.data, UA_PATH_MAX - rootDirectorySize); UA_String_clear(&nodeIdStr); } - rootDirectorySize = strnlen(rootDirectory, PATH_MAX); + rootDirectorySize = strnlen(rootDirectory, UA_PATH_MAX); context->rootFolder = UA_STRING_ALLOC(rootDirectory); @@ -450,6 +449,8 @@ FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String return retval; } +#ifdef __linux__ + static UA_StatusCode FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { if(certGroup == NULL) @@ -461,8 +462,8 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { if(context->inotifyFd == -1) return UA_STATUSCODE_BADINTERNALERROR; - char folder[PATH_MAX] = {0}; - mp_snprintf(folder, PATH_MAX, "%.*s", + char folder[UA_PATH_MAX] = {0}; + mp_snprintf(folder, UA_PATH_MAX, "%.*s", (int)context->rootFolder.length, (char*)context->rootFolder.data); int wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); if(wd == -1) { @@ -471,7 +472,7 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { return UA_STATUSCODE_BADINTERNALERROR; } - mp_snprintf(folder, PATH_MAX, "%.*s", + mp_snprintf(folder, UA_PATH_MAX, "%.*s", (int)context->trustedCertFolder.length, (char*)context->trustedCertFolder.data); wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); if(wd == -1) { @@ -483,6 +484,8 @@ FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { return UA_STATUSCODE_GOOD; } +#endif /* __linux__ */ + static UA_StatusCode FileCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) { /* Check parameter */ @@ -634,8 +637,10 @@ FileCertStore_clear(UA_CertificateGroup *certGroup) { UA_String_clear(&context->ownKeyFolder); UA_String_clear(&context->rootFolder); +#ifdef __linux__ if(context->inotifyFd > 0) close(context->inotifyFd); +#endif /* __linux__ */ UA_free(context); certGroup->context = NULL; @@ -688,10 +693,12 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup, goto cleanup; } +#ifdef __linux__ retval = FileCertStore_createInotifyEvent(certGroup); if(retval != UA_STATUSCODE_GOOD) { goto cleanup; } +#endif /* __linux__ */ retval = reloadAndWriteTrustStore(certGroup); if(retval != UA_STATUSCODE_GOOD) { @@ -705,6 +712,6 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup, return retval; } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ diff --git a/plugins/crypto/ua_filestore_common.c b/plugins/crypto/ua_filestore_common.c index 902156d8c..41653ed47 100644 --- a/plugins/crypto/ua_filestore_common.c +++ b/plugins/crypto/ua_filestore_common.c @@ -6,11 +6,21 @@ */ #include "ua_filestore_common.h" -#include #ifdef UA_ENABLE_ENCRYPTION -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) + +#ifdef UA_ARCHITECTURE_WIN32 +/* TODO: Replace with a proper dirname implementation. This is a just minimal + * implementation working with correct input data. */ +char * +_UA_dirname_minimal(char *path) { + char *lastSlash = strrchr(path, '/'); + *lastSlash = 0; + return path; +} +#endif /* UA_ARCHITECTURE_WIN32 */ UA_StatusCode readFileToByteString(const char *const path, UA_ByteString *data) { @@ -18,23 +28,23 @@ readFileToByteString(const char *const path, UA_ByteString *data) { return UA_STATUSCODE_BADINTERNALERROR; /* Open the file */ - FILE *fp = fopen(path, "rb"); + UA_FILE *fp = UA_fopen(path, "rb"); if(!fp) return UA_STATUSCODE_BADNOTFOUND; /* Get the file length, allocate the data and read */ - fseek(fp, 0, SEEK_END); - UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp)); + UA_fseek(fp, 0, UA_SEEK_END); + UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)UA_ftell(fp)); if(retval == UA_STATUSCODE_GOOD) { - fseek(fp, 0, SEEK_SET); - size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); + UA_fseek(fp, 0, UA_SEEK_SET); + size_t read = UA_fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); if(read != data->length) { UA_ByteString_clear(data); } } else { data->length = 0; } - fclose(fp); + UA_fclose(fp); return UA_STATUSCODE_GOOD; } @@ -44,21 +54,21 @@ writeByteStringToFile(const char *const path, const UA_ByteString *data) { UA_StatusCode retval = UA_STATUSCODE_GOOD; /* Open the file */ - FILE *fp = fopen(path, "wb"); + UA_FILE *fp = UA_fopen(path, "wb"); if(!fp) return UA_STATUSCODE_BADINTERNALERROR; /* Write byte string to file */ - size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); + size_t len = UA_fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp); if(len != data->length) { - fclose(fp); + UA_fclose(fp); retval = UA_STATUSCODE_BADINTERNALERROR; } - fclose(fp); + UA_fclose(fp); return retval; } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ diff --git a/plugins/crypto/ua_filestore_common.h b/plugins/crypto/ua_filestore_common.h index 348a46472..4e1320880 100644 --- a/plugins/crypto/ua_filestore_common.h +++ b/plugins/crypto/ua_filestore_common.h @@ -9,7 +9,95 @@ #ifdef UA_ENABLE_ENCRYPTION -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) + +#if defined(UA_ARCHITECTURE_WIN32) + +#include +#include +#include +#include +#include +#include "dirent.h" + +_UA_BEGIN_DECLS +char * +_UA_dirname_minimal(char *path); +_UA_END_DECLS + +#define UA_STAT stat +#define UA_DIR DIR +#define UA_DIRENT dirent +#define UA_FILE FILE +#define UA_MODE uint16_t + +#define UA_stat stat +#define UA_opendir opendir +#define UA_readdir readdir +#define UA_rewinddir rewinddir +#define UA_closedir closedir +#define UA_mkdir(path, mode) _mkdir(path) +#define UA_fopen fopen +#define UA_fread fread +#define UA_fwrite fwrite +#define UA_fseek fseek +#define UA_ftell ftell +#define UA_fclose fclose +#define UA_remove remove +#define UA_dirname _UA_dirname_minimal + +#define UA_SEEK_END SEEK_END +#define UA_SEEK_SET SEEK_SET +#define UA_DT_REG DT_REG +#define UA_DT_DIR DT_DIR +#define UA_PATH_MAX MAX_PATH +#define UA_FILENAME_MAX FILENAME_MAX + +#elif defined(__linux__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __ANDROID__ +#include +#endif /* !__ANDROID__ */ + +#define UA_STAT stat +#define UA_DIR DIR +#define UA_DIRENT dirent +#define UA_FILE FILE +#define UA_MODE mode_t + +#define UA_stat stat +#define UA_opendir opendir +#define UA_readdir readdir +#define UA_rewinddir rewinddir +#define UA_closedir closedir +#define UA_mkdir mkdir +#define UA_fopen fopen +#define UA_fread fread +#define UA_fwrite fwrite +#define UA_fseek fseek +#define UA_ftell ftell +#define UA_fclose fclose +#define UA_remove remove +#define UA_dirname dirname + +#define UA_SEEK_END SEEK_END +#define UA_SEEK_SET SEEK_SET +#define UA_DT_REG DT_REG +#define UA_DT_DIR DT_DIR +#define UA_PATH_MAX PATH_MAX +#define UA_FILENAME_MAX FILENAME_MAX + +#endif UA_StatusCode readFileToByteString(const char *const path, @@ -19,6 +107,6 @@ UA_StatusCode writeByteStringToFile(const char *const path, const UA_ByteString *data); -#endif /* __linux__ */ +#endif /* __linux__ || UA_ARCHITECTURE_WIN32 */ #endif /* UA_ENABLE_ENCRYPTION */ diff --git a/plugins/crypto/ua_securitypolicy_filestore.c b/plugins/crypto/ua_securitypolicy_filestore.c index da7763ee8..0cb3fe525 100644 --- a/plugins/crypto/ua_securitypolicy_filestore.c +++ b/plugins/crypto/ua_securitypolicy_filestore.c @@ -14,14 +14,7 @@ #ifdef UA_ENABLE_ENCRYPTION -#ifdef __linux__ /* Linux only so far */ - -#include -#include - -#ifndef __ANDROID__ -#include -#endif // !__ANDROID__ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) typedef struct { /* In-Memory security policy as a base */ @@ -35,18 +28,18 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) { UA_StatusCode retval = UA_STATUSCODE_GOOD; bool isStored = false; UA_ByteString fileData = UA_BYTESTRING_NULL; - DIR *dir = opendir(path); + UA_DIR *dir = UA_opendir(path); if(!dir) return UA_STATUSCODE_BADINTERNALERROR; - struct dirent *dirent; - while((dirent = readdir(dir)) != NULL) { - if(dirent->d_type != DT_REG) + struct UA_DIRENT *dirent; + while((dirent = UA_readdir(dir)) != NULL) { + if(dirent->d_type != UA_DT_REG) continue; /* Get filename to load */ - char filename[FILENAME_MAX]; - if(mp_snprintf(filename, FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0) + char filename[UA_FILENAME_MAX]; + if(mp_snprintf(filename, UA_FILENAME_MAX, "%s/%s", path, dirent->d_name) < 0) return false; /* Load data from file */ @@ -66,7 +59,7 @@ checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) { cleanup: if(fileData.length > 0) UA_ByteString_clear(&fileData); - closedir(dir); + UA_closedir(dir); return isStored; } @@ -129,13 +122,13 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath, UA_StatusCode retval = UA_STATUSCODE_GOOD; /* Create the paths to the certificates and private key folders */ - char ownCertPathDir[PATH_MAX]; - if(mp_snprintf(ownCertPathDir, PATH_MAX, "%.*s%s", (int)storePath.length, + char ownCertPathDir[UA_PATH_MAX]; + if(mp_snprintf(ownCertPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length, (char *)storePath.data, "/ApplCerts/own/certs") < 0) return UA_STATUSCODE_BADINTERNALERROR; - char ownKeyPathDir[PATH_MAX]; - if(mp_snprintf(ownKeyPathDir, PATH_MAX, "%.*s%s", (int)storePath.length, + char ownKeyPathDir[UA_PATH_MAX]; + if(mp_snprintf(ownKeyPathDir, UA_PATH_MAX, "%.*s%s", (int)storePath.length, (char *)storePath.data, "/ApplCerts/own/private") < 0) return UA_STATUSCODE_BADINTERNALERROR; @@ -144,24 +137,28 @@ writeCertificateAndPrivateKeyToFilestore(const UA_String storePath, return UA_STATUSCODE_GOOD; /* Create filename for new certificate */ - char newFilename[PATH_MAX]; - retval = createCertName(&newCertificate, newFilename, PATH_MAX); + char newFilename[UA_PATH_MAX]; + retval = createCertName(&newCertificate, newFilename, UA_PATH_MAX); if(retval != UA_STATUSCODE_GOOD) return retval; - char newCertFilename[PATH_MAX]; - if(mp_snprintf(newCertFilename, PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0) + char newCertFilename[UA_PATH_MAX]; + if(mp_snprintf(newCertFilename, UA_PATH_MAX, "%s/%s%s", ownCertPathDir, newFilename, ".der") < 0) return UA_STATUSCODE_BADINTERNALERROR; - char newKeyFilename[PATH_MAX]; - if(mp_snprintf(newKeyFilename, PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0) + char newKeyFilename[UA_PATH_MAX]; + if(mp_snprintf(newKeyFilename, UA_PATH_MAX, "%s/%s%s", ownKeyPathDir, newFilename, ".key") < 0) return UA_STATUSCODE_BADINTERNALERROR; retval = writeByteStringToFile(newCertFilename, &newCertificate); if(retval != UA_STATUSCODE_GOOD) return retval; - return writeByteStringToFile(newKeyFilename, &newPrivateKey); + /* Write new private key only if it is set */ + if(newPrivateKey.length > 0) + return writeByteStringToFile(newKeyFilename, &newPrivateKey); + else + return UA_STATUSCODE_GOOD; } /********************/ @@ -347,6 +344,6 @@ UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy, return retval; } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ diff --git a/plugins/include/open62541/plugin/certificategroup_default.h b/plugins/include/open62541/plugin/certificategroup_default.h index 9aa71512d..3c1ed12eb 100644 --- a/plugins/include/open62541/plugin/certificategroup_default.h +++ b/plugins/include/open62541/plugin/certificategroup_default.h @@ -39,7 +39,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup, const UA_Logger *logger, const UA_KeyValueMap *params); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) /* * Initialises and configures a certificate group with a filestore backend. * @@ -92,9 +92,9 @@ UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup, const UA_String storePath, const UA_Logger *logger, const UA_KeyValueMap *params); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ _UA_END_DECLS diff --git a/plugins/include/open62541/plugin/securitypolicy_default.h b/plugins/include/open62541/plugin/securitypolicy_default.h index 1d1ee3502..8a288fb75 100644 --- a/plugins/include/open62541/plugin/securitypolicy_default.h +++ b/plugins/include/open62541/plugin/securitypolicy_default.h @@ -58,14 +58,14 @@ UA_SecurityPolicy_EccNistP256(UA_SecurityPolicy *policy, const UA_ByteString localPrivateKey, const UA_Logger *logger); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) UA_EXPORT UA_StatusCode UA_SecurityPolicy_Filestore(UA_SecurityPolicy *policy, UA_SecurityPolicy *innerPolicy, const UA_String storePath); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ UA_EXPORT UA_StatusCode UA_PubSubSecurityPolicy_Aes128Ctr(UA_PubSubSecurityPolicy *policy, diff --git a/plugins/include/open62541/server_config_default.h b/plugins/include/open62541/server_config_default.h index a2cfadb1d..7449dae70 100644 --- a/plugins/include/open62541/server_config_default.h +++ b/plugins/include/open62541/server_config_default.h @@ -86,7 +86,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf, const UA_ByteString *revocationList, size_t revocationListSize); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) UA_EXPORT UA_StatusCode UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf, @@ -95,9 +95,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf, const UA_ByteString *privateKey, const UA_String storePath); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ /* Creates a server config on the default port 4840 with no server * certificate. */ @@ -257,7 +257,7 @@ UA_ServerConfig_addAllSecureSecurityPolicies(UA_ServerConfig *config, const UA_ByteString *certificate, const UA_ByteString *privateKey); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) /* Adds a filestore security policy based on a given security policy to the server. * @@ -289,9 +289,9 @@ UA_ServerConfig_addSecurityPolicies_Filestore(UA_ServerConfig *config, const UA_ByteString *certificate, const UA_ByteString *privateKey, const UA_String storePath); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ /* Adds an endpoint for the given security policy and mode. The security * policy has to be added already. See UA_ServerConfig_addXxx functions. diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index 6fb4622f4..6356583de 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -1221,7 +1221,7 @@ UA_ServerConfig_setDefaultWithSecureSecurityPolicies(UA_ServerConfig *conf, return UA_STATUSCODE_GOOD; } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) UA_StatusCode UA_ServerConfig_addSecurityPolicy_Filestore(UA_ServerConfig *config, @@ -1580,9 +1580,9 @@ UA_ServerConfig_setDefaultWithFilestore(UA_ServerConfig *conf, return retval; } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ -#endif +#endif /* UA_ENABLE_ENCRYPTION */ /***************************/ /* Default Client Settings */ diff --git a/plugins/ua_config_json.c b/plugins/ua_config_json.c index 3b5d12d23..bd3090ac9 100644 --- a/plugins/ua_config_json.c +++ b/plugins/ua_config_json.c @@ -773,7 +773,7 @@ PARSE_JSON(SecurityPkiField) { if(retval != UA_STATUSCODE_GOOD) return retval; -#ifndef __linux__ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) /* Currently not supported! */ (void)config; return UA_STATUSCODE_GOOD; diff --git a/tests/encryption/check_certificategroup.c b/tests/encryption/check_certificategroup.c index 2216c51ab..da0cb8fe3 100644 --- a/tests/encryption/check_certificategroup.c +++ b/tests/encryption/check_certificategroup.c @@ -23,6 +23,11 @@ #include "check.h" #include "thread_wrapper.h" +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) +#include "mp_printf.h" +#define TEST_PATH_MAX 256 +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ + UA_Server *server; UA_Boolean running; THREAD_HANDLE server_thread; @@ -60,7 +65,7 @@ static void setup(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -81,9 +86,9 @@ static void setup2(void) { ck_assert(server != NULL); UA_ServerConfig *config = UA_Server_getConfig(server); - char storePathDir[32]; - strcpy(storePathDir, "open62541-pki-XXXXXX"); - mkdtemp(storePathDir); + char storePathDir[TEST_PATH_MAX]; + getcwd(storePathDir, TEST_PATH_MAX - 4); + mp_snprintf(storePathDir, TEST_PATH_MAX, "%s/pki", storePathDir); const UA_String storePath = UA_STRING(storePathDir); @@ -97,11 +102,21 @@ static void setup2(void) { config->applicationDescription.applicationUri = UA_STRING_ALLOC("urn:unconfigured:application"); + /* Clear old certificates */ + UA_ByteString empty[2] = {0}; + UA_NodeId defaultApplicationGroup = UA_NODEID_NUMERIC( + 0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP); + UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, + empty, 0, empty, 0, true, false); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + retval = UA_Server_addCertificates(server, defaultApplicationGroup, empty, 0, empty, + 0, false, false); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif - +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -330,7 +345,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_memorystore); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("CertificateGroup Filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -341,7 +356,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, get_rejectedlist); suite_add_tcase(s,tc_encryption_filestore); #endif /* UA_ENABLE_ENCRYPTION */ -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_aes128sha256rsaoaep.c b/tests/encryption/check_encryption_aes128sha256rsaoaep.c index fb842444d..b592bb148 100644 --- a/tests/encryption/check_encryption_aes128sha256rsaoaep.c +++ b/tests/encryption/check_encryption_aes128sha256rsaoaep.c @@ -91,7 +91,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -125,7 +125,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -308,7 +308,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption Aes128Sha256RsaOaep security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -316,7 +316,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_aes256sha256rsapss.c b/tests/encryption/check_encryption_aes256sha256rsapss.c index 758e82394..839aef8fd 100644 --- a/tests/encryption/check_encryption_aes256sha256rsapss.c +++ b/tests/encryption/check_encryption_aes256sha256rsapss.c @@ -91,7 +91,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -125,7 +125,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -308,7 +308,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption Aes256Sha256RsaPss security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -316,7 +316,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_basic128rsa15.c b/tests/encryption/check_encryption_basic128rsa15.c index 6a464f6b3..df24daacc 100644 --- a/tests/encryption/check_encryption_basic128rsa15.c +++ b/tests/encryption/check_encryption_basic128rsa15.c @@ -99,7 +99,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -133,7 +133,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -350,7 +350,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption basic128rsa15 security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -358,7 +358,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_basic256.c b/tests/encryption/check_encryption_basic256.c index 839e1533a..3ad76c492 100644 --- a/tests/encryption/check_encryption_basic256.c +++ b/tests/encryption/check_encryption_basic256.c @@ -103,7 +103,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -137,7 +137,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -354,7 +354,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption basic256 security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -362,7 +362,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_basic256sha256.c b/tests/encryption/check_encryption_basic256sha256.c index 609364d04..2879ad0a9 100644 --- a/tests/encryption/check_encryption_basic256sha256.c +++ b/tests/encryption/check_encryption_basic256sha256.c @@ -91,7 +91,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -125,7 +125,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -308,7 +308,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption basic256sha256 security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -316,7 +316,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_encryption_eccnistp256.c b/tests/encryption/check_encryption_eccnistp256.c index 486a235c1..dbf76bb5f 100644 --- a/tests/encryption/check_encryption_eccnistp256.c +++ b/tests/encryption/check_encryption_eccnistp256.c @@ -91,7 +91,7 @@ static void setup(void) { THREAD_CREATE(server_thread, serverloop); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { running = true; @@ -125,7 +125,7 @@ static void setup2(void) { UA_Server_run_startup(server); THREAD_CREATE(server_thread, serverloop); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { running = false; @@ -308,7 +308,7 @@ static Suite* testSuite_encryption(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_encryption_filestore = tcase_create("Encryption ECC_nistP256 security policy filestore"); tcase_add_checked_fixture(tc_encryption_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION @@ -316,7 +316,7 @@ static Suite* testSuite_encryption(void) { tcase_add_test(tc_encryption_filestore, encryption_connect_pem); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_encryption_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_update_certificate.c b/tests/encryption/check_update_certificate.c index 67334674a..9c8b42c5e 100644 --- a/tests/encryption/check_update_certificate.c +++ b/tests/encryption/check_update_certificate.c @@ -15,10 +15,10 @@ #include "test_helpers.h" #include "certificates.h" -#ifdef __linux__ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) #include "mp_printf.h" -#include -#endif +#define TEST_PATH_MAX 256 +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ UA_Server *server; @@ -37,7 +37,7 @@ static void setup(void) { ck_assert(server != NULL); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { /* Load certificate and private key */ UA_ByteString certificate; @@ -48,9 +48,9 @@ static void setup2(void) { privateKey.length = KEY_DER_LENGTH; privateKey.data = KEY_DER_DATA; - char storePathDir[PATH_MAX]; - getcwd(storePathDir, PATH_MAX - 4); - mp_snprintf(storePathDir, PATH_MAX, "%s/pki", storePathDir); + char storePathDir[TEST_PATH_MAX]; + getcwd(storePathDir, TEST_PATH_MAX - 4); + mp_snprintf(storePathDir, TEST_PATH_MAX, "%s/pki", storePathDir); const UA_String storePath = UA_STRING(storePathDir); server = @@ -58,7 +58,7 @@ static void setup2(void) { &privateKey, storePath); ck_assert(server != NULL); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { UA_Server_delete(server); @@ -156,16 +156,16 @@ static Suite* testSuite_create_certificate(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_cert); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_cert_filestore = tcase_create("Update Certificate Filestore"); tcase_add_checked_fixture(tc_cert_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION tcase_add_test(tc_cert_filestore, update_certificate); - tcase_add_test(tc_cert, update_certificate_wrongKey); - tcase_add_test(tc_cert, update_certificate_noKey); + tcase_add_test(tc_cert_filestore, update_certificate_wrongKey); + tcase_add_test(tc_cert_filestore, update_certificate_noKey); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_cert_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/encryption/check_update_trustlist.c b/tests/encryption/check_update_trustlist.c index 24c2b195e..c351c73fe 100644 --- a/tests/encryption/check_update_trustlist.c +++ b/tests/encryption/check_update_trustlist.c @@ -15,10 +15,10 @@ #include "test_helpers.h" #include "certificates.h" -#ifdef __linux__ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) #include "mp_printf.h" -#include -#endif +#define TEST_PATH_MAX 256 +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ UA_Server *server; @@ -37,7 +37,7 @@ static void setup(void) { ck_assert(server != NULL); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static void setup2(void) { /* Load certificate and private key */ UA_ByteString certificate; @@ -48,17 +48,28 @@ static void setup2(void) { privateKey.length = KEY_DER_LENGTH; privateKey.data = KEY_DER_DATA; - char storePathDir[PATH_MAX]; - getcwd(storePathDir, PATH_MAX - 4); - mp_snprintf(storePathDir, PATH_MAX, "%s/pki", storePathDir); + char storePathDir[TEST_PATH_MAX]; + getcwd(storePathDir, TEST_PATH_MAX - 4); + mp_snprintf(storePathDir, TEST_PATH_MAX, "%s/pki", storePathDir); const UA_String storePath = UA_STRING(storePathDir); server = UA_Server_newForUnitTestWithSecurityPolicies_Filestore(4840, &certificate, &privateKey, storePath); ck_assert(server != NULL); + + /* Clear old certificates */ + UA_ByteString empty[2] = {0}; + UA_NodeId defaultApplicationGroup = UA_NODEID_NUMERIC( + 0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP); + UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, + empty, 0, empty, 0, true, false); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); + retval = UA_Server_addCertificates(server, defaultApplicationGroup, empty, 0, empty, + 0, false, false); + ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static void teardown(void) { UA_Server_delete(server); @@ -303,18 +314,18 @@ static Suite* testSuite_create_certificate(void) { #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_cert); -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) TCase *tc_cert_filestore = tcase_create("Update Certificate Filestore"); tcase_add_checked_fixture(tc_cert_filestore, setup2, teardown); #ifdef UA_ENABLE_ENCRYPTION - tcase_add_test(tc_cert, add_ca_certificate_trustlist); - tcase_add_test(tc_cert, add_ca_certificate_issuerlist); - tcase_add_test(tc_cert, remove_certificate_trustlist); - tcase_add_test(tc_cert, remove_certificate_issuerlist); - tcase_add_test(tc_cert, add_application_certificate_trustlist); + tcase_add_test(tc_cert_filestore, add_ca_certificate_trustlist); + tcase_add_test(tc_cert_filestore, add_ca_certificate_issuerlist); + tcase_add_test(tc_cert_filestore, remove_certificate_trustlist); + tcase_add_test(tc_cert_filestore, remove_certificate_issuerlist); + tcase_add_test(tc_cert_filestore, add_application_certificate_trustlist); #endif /* UA_ENABLE_ENCRYPTION */ suite_add_tcase(s,tc_cert_filestore); -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ return s; } diff --git a/tests/testing-plugins/test_helpers.h b/tests/testing-plugins/test_helpers.h index 0b82c7c2c..85e395a7c 100644 --- a/tests/testing-plugins/test_helpers.h +++ b/tests/testing-plugins/test_helpers.h @@ -48,7 +48,7 @@ UA_Server_newForUnitTestWithSecurityPolicies(UA_UInt16 portNumber, return UA_Server_newWithConfig(&config); } -#ifdef __linux__ /* Linux only so far */ +#if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) static UA_INLINE UA_Server * UA_Server_newForUnitTestWithSecurityPolicies_Filestore(UA_UInt16 portNumber, const UA_ByteString *certificate, @@ -65,7 +65,7 @@ UA_Server_newForUnitTestWithSecurityPolicies_Filestore(UA_UInt16 portNumber, config.tcpReuseAddr = true; return UA_Server_newWithConfig(&config); } -#endif +#endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ static UA_INLINE UA_Client * UA_Client_newForUnitTest(void) { From 0cde335fa79ee39af1541ac0378841807e891973 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Tue, 14 Jan 2025 17:10:53 +0100 Subject: [PATCH 122/158] fix(plugin): Initialize memory to prevent uninitialized memory error --- plugins/crypto/ua_certificategroup_filestore.c | 6 +++--- plugins/crypto/ua_securitypolicy_filestore.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/crypto/ua_certificategroup_filestore.c b/plugins/crypto/ua_certificategroup_filestore.c index 160426baf..2fbe6f174 100644 --- a/plugins/crypto/ua_certificategroup_filestore.c +++ b/plugins/crypto/ua_certificategroup_filestore.c @@ -115,7 +115,7 @@ getCertFileName(const char *path, const UA_ByteString *certificate, UA_String thumbprint = UA_STRING_NULL; thumbprint.length = 40; - thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length); + thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte)); UA_String subjectName = UA_STRING_NULL; @@ -194,7 +194,7 @@ readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) { continue; if(numActCerts < numCerts) { /* Create filename to load */ - char filename[UA_PATH_MAX]; + char filename[UA_PATH_MAX] = {0}; if(mp_snprintf(filename, UA_PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) { UA_closedir(dir); return UA_STATUSCODE_BADINTERNALERROR; @@ -293,7 +293,7 @@ writeCertificates(UA_CertificateGroup *certGroup, const UA_ByteString *list, UA_StatusCode retval = UA_STATUSCODE_GOOD; for(size_t i = 0; i < listSize; i++) { /* Create filename to load */ - char filename[UA_PATH_MAX]; + char filename[UA_PATH_MAX] = {0}; retval = getCertFileName(listPath, &list[i], filename, UA_PATH_MAX); if(retval != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADINTERNALERROR; diff --git a/plugins/crypto/ua_securitypolicy_filestore.c b/plugins/crypto/ua_securitypolicy_filestore.c index 0cb3fe525..399f60e95 100644 --- a/plugins/crypto/ua_securitypolicy_filestore.c +++ b/plugins/crypto/ua_securitypolicy_filestore.c @@ -74,7 +74,7 @@ createCertName(const UA_ByteString *certificate, char *fileNameBuf, size_t fileN UA_String thumbprint = UA_STRING_NULL; thumbprint.length = 40; - thumbprint.data = (UA_Byte*)UA_malloc(sizeof(UA_Byte)*thumbprint.length); + thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte)); UA_String subjectName = UA_STRING_NULL; From 949b0cc4dd227706ecab623ae5836e4023f67d5b Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Tue, 14 Jan 2025 17:13:51 +0100 Subject: [PATCH 123/158] fix(test): Resolve incorrect checks caused by existing certificates in PKI folder --- tests/encryption/check_update_trustlist.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/tests/encryption/check_update_trustlist.c b/tests/encryption/check_update_trustlist.c index c351c73fe..01e58c0fc 100644 --- a/tests/encryption/check_update_trustlist.c +++ b/tests/encryption/check_update_trustlist.c @@ -57,17 +57,6 @@ static void setup2(void) { UA_Server_newForUnitTestWithSecurityPolicies_Filestore(4840, &certificate, &privateKey, storePath); ck_assert(server != NULL); - - /* Clear old certificates */ - UA_ByteString empty[2] = {0}; - UA_NodeId defaultApplicationGroup = UA_NODEID_NUMERIC( - 0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP); - UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, - empty, 0, empty, 0, true, false); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_Server_addCertificates(server, defaultApplicationGroup, empty, 0, empty, - 0, false, false); - ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); } #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ @@ -122,7 +111,7 @@ START_TEST(add_ca_certificate_trustlist) { UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, trustedCertificates, 2, - trustedCrls, 2, true, true); + trustedCrls, 2, true, false); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); UA_TrustListDataType trustList; @@ -163,7 +152,7 @@ START_TEST(add_ca_certificate_issuerlist) { UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, issuerCertificates, 2, - issuerCrls, 2, false, true); + issuerCrls, 2, false, false); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); UA_TrustListDataType trustList; @@ -204,7 +193,7 @@ START_TEST(remove_certificate_trustlist) { UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, trustedCertificates, 2, - trustedCrls, 2, true, true); + trustedCrls, 2, true, false); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); retval = UA_Server_removeCertificates(server, defaultApplicationGroup, @@ -249,7 +238,7 @@ START_TEST(remove_certificate_issuerlist) { UA_StatusCode retval = UA_Server_addCertificates(server, defaultApplicationGroup, issuerCertificates, 2, - issuerCrls, 2, false, true); + issuerCrls, 2, false, false); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); retval = UA_Server_removeCertificates(server, defaultApplicationGroup, From 06d0d3ff866881c2c64529d26c5b599d242cc786 Mon Sep 17 00:00:00 2001 From: Tomi Takala Date: Mon, 13 Jan 2025 16:36:22 +0200 Subject: [PATCH 124/158] fix(plugins): Add include guard to ua_filestore_common.h --- plugins/crypto/ua_filestore_common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/crypto/ua_filestore_common.h b/plugins/crypto/ua_filestore_common.h index 4e1320880..49438b27c 100644 --- a/plugins/crypto/ua_filestore_common.h +++ b/plugins/crypto/ua_filestore_common.h @@ -5,6 +5,9 @@ * Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf) */ +#ifndef UA_FILESTORE_COMMON_H_ +#define UA_FILESTORE_COMMON_H_ + #include #ifdef UA_ENABLE_ENCRYPTION @@ -110,3 +113,5 @@ writeByteStringToFile(const char *const path, #endif /* __linux__ || UA_ARCHITECTURE_WIN32 */ #endif /* UA_ENABLE_ENCRYPTION */ + +#endif /* UA_FILESTORE_COMMON_H_ */ From 931cc824f7ca3da4c7a4b08b15c135b124153f96 Mon Sep 17 00:00:00 2001 From: Tomi Takala Date: Mon, 13 Jan 2025 16:53:31 +0200 Subject: [PATCH 125/158] fix(plugins): Rename dirent.h to tr_dirent.h --- CMakeLists.txt | 2 +- deps/README.md | 2 +- deps/{dirent.h => tr_dirent.h} | 0 plugins/crypto/ua_filestore_common.h | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename deps/{dirent.h => tr_dirent.h} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 454cc63db..6a3dc2ad6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -810,7 +810,7 @@ set(lib_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h ${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h) if(UA_ENABLE_ENCRYPTION AND UA_ARCHITECTURE_WIN32) - list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/deps/dirent.h) + list(APPEND lib_headers ${PROJECT_SOURCE_DIR}/deps/tr_dirent.h) endif() set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c diff --git a/deps/README.md b/deps/README.md index b2c71067a..405327506 100644 --- a/deps/README.md +++ b/deps/README.md @@ -22,4 +22,4 @@ The following third party libraries may be included -- depending on the activate | dtoa | BSL (Boost) | Printing of float numbers | | mp_printf | MIT | Our version of github:mpaland/printf | | utf8 | MPL 2.0 | Lightweight utf8 de/encoding | -| dirent | MIT | Dirent interface for Microsoft Visual Studio | +| tr_dirent | MIT | Dirent interface for Microsoft Visual Studio | diff --git a/deps/dirent.h b/deps/tr_dirent.h similarity index 100% rename from deps/dirent.h rename to deps/tr_dirent.h diff --git a/plugins/crypto/ua_filestore_common.h b/plugins/crypto/ua_filestore_common.h index 49438b27c..720a3a234 100644 --- a/plugins/crypto/ua_filestore_common.h +++ b/plugins/crypto/ua_filestore_common.h @@ -21,7 +21,7 @@ #include #include #include -#include "dirent.h" +#include "tr_dirent.h" _UA_BEGIN_DECLS char * From f29a7f3f30ae19a0e4b69742a4fef097af862c66 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Wed, 15 Jan 2025 12:49:46 +0100 Subject: [PATCH 126/158] fix(plugin): Extend CertificateUtils to support CRL for the thumbprint and subject name functions --- plugins/crypto/mbedtls/certificategroup.c | 20 ++++++++--- plugins/crypto/openssl/certificategroup.c | 42 ++++++++++++++++------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index 41ae76f58..da7f5611f 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -837,13 +837,23 @@ UA_CertificateUtils_getSubjectName(UA_ByteString *certificate, mbedtls_x509_crt publicKey; mbedtls_x509_crt_init(&publicKey); - UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey); - if(retval != UA_STATUSCODE_GOOD) - return retval; + mbedtls_x509_crl crl; + mbedtls_x509_crl_init(&crl); char buf[1024]; - int res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject); - mbedtls_x509_crt_free(&publicKey); + int res = 0; + UA_StatusCode retval = UA_mbedTLS_LoadCertificate(certificate, &publicKey); + if(retval == UA_STATUSCODE_GOOD) { + res = mbedtls_x509_dn_gets(buf, 1024, &publicKey.subject); + mbedtls_x509_crt_free(&publicKey); + } else { + retval = UA_mbedTLS_LoadCrl(certificate, &crl); + if(retval != UA_STATUSCODE_GOOD) + return retval; + res = mbedtls_x509_dn_gets(buf, 1024, &crl.issuer); + mbedtls_x509_crl_free(&crl); + } + if(res < 0) return UA_STATUSCODE_BADINTERNALERROR; UA_String tmp = {(size_t)res, (UA_Byte*)buf}; diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index 4154e191b..14081cfb9 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -858,14 +858,24 @@ UA_CertificateUtils_getExpirationDate(UA_ByteString *certificate, UA_StatusCode UA_CertificateUtils_getSubjectName(UA_ByteString *certificate, UA_String *subjectName) { + X509_NAME *sn = NULL; X509 *x509 = UA_OpenSSL_LoadCertificate(certificate); - if(!x509) - return UA_STATUSCODE_BADSECURITYCHECKSFAILED; + X509_CRL *x509_crl = NULL; + + if(x509) { + sn = X509_get_subject_name(x509); + } else { + x509_crl = UA_OpenSSL_LoadCrl(certificate); + if(!x509_crl) + return UA_STATUSCODE_BADSECURITYCHECKSFAILED; + sn = X509_CRL_get_issuer(x509_crl); + } - X509_NAME *sn = X509_get_subject_name(x509); char buf[1024]; *subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024)); - X509_free(x509); + + if (x509) X509_free(x509); + if (x509_crl) X509_CRL_free(x509_crl); return UA_STATUSCODE_GOOD; } @@ -875,15 +885,25 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate, if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2)) return UA_STATUSCODE_BADINTERNALERROR; - X509 *cert = UA_OpenSSL_LoadCertificate(certificate); - if(!cert) - return UA_STATUSCODE_BADSECURITYCHECKSFAILED; - unsigned char digest[SHA1_DIGEST_LENGTH]; unsigned int digestLen; - if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) { + + X509 *cert = UA_OpenSSL_LoadCertificate(certificate); + if(cert) { + if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) { + X509_free(cert); + return UA_STATUSCODE_BADINTERNALERROR; + } X509_free(cert); - return UA_STATUSCODE_BADINTERNALERROR; + } else { + X509_CRL *crl = UA_OpenSSL_LoadCrl(certificate); + if(!crl) + return UA_STATUSCODE_BADSECURITYCHECKSFAILED; + if(X509_CRL_digest(crl, EVP_sha1(), digest, &digestLen) != 1) { + X509_CRL_free(crl); + return UA_STATUSCODE_BADINTERNALERROR; + } + X509_CRL_free(crl); } UA_String thumb = UA_STRING_NULL; @@ -897,8 +917,6 @@ UA_CertificateUtils_getThumbprint(UA_ByteString *certificate, } memcpy(thumbprint->data, thumb.data, thumbprint->length); - - X509_free(cert); free(thumb.data); return UA_STATUSCODE_GOOD; From 50fcaccd64dc4a939d5c358c998ec7f245661294 Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Wed, 15 Jan 2025 09:59:25 +0100 Subject: [PATCH 127/158] feat(util): Adding TrustListDataType utility function to set a specific part of the trust list --- include/open62541/util.h | 4 ++++ src/util/ua_util.c | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/include/open62541/util.h b/include/open62541/util.h index 47ab4662c..4cd44160f 100644 --- a/include/open62541/util.h +++ b/include/open62541/util.h @@ -439,6 +439,10 @@ UA_ByteString_memZero(UA_ByteString *bs); UA_EXPORT UA_StatusCode UA_TrustListDataType_add(const UA_TrustListDataType *src, UA_TrustListDataType *dst); +/* Replaces the contents of the destination trusted list with the certificates from the source trusted list. */ +UA_EXPORT UA_StatusCode +UA_TrustListDataType_set(const UA_TrustListDataType *src, UA_TrustListDataType *dst); + /* Removes all of the certificates from the dst trust list that are specified * in the src trust list. */ UA_EXPORT UA_StatusCode diff --git a/src/util/ua_util.c b/src/util/ua_util.c index 17a719965..168be5b70 100644 --- a/src/util/ua_util.c +++ b/src/util/ua_util.c @@ -1133,6 +1133,31 @@ UA_TrustListDataType_add(const UA_TrustListDataType *src, UA_TrustListDataType * return retval; } +UA_StatusCode +UA_TrustListDataType_set(const UA_TrustListDataType *src, UA_TrustListDataType *dst) { + if(src->specifiedLists & UA_TRUSTLISTMASKS_TRUSTEDCERTIFICATES) { + UA_Array_delete(dst->trustedCertificates, dst->trustedCertificatesSize, &UA_TYPES[UA_TYPES_BYTESTRING]); + dst->trustedCertificates = NULL; + dst->trustedCertificatesSize = 0; + } + if(src->specifiedLists & UA_TRUSTLISTMASKS_TRUSTEDCRLS) { + UA_Array_delete(dst->trustedCrls, dst->trustedCrlsSize, &UA_TYPES[UA_TYPES_BYTESTRING]); + dst->trustedCrls = NULL; + dst->trustedCrlsSize = 0; + } + if(src->specifiedLists & UA_TRUSTLISTMASKS_ISSUERCERTIFICATES) { + UA_Array_delete(dst->issuerCertificates, dst->issuerCertificatesSize, &UA_TYPES[UA_TYPES_BYTESTRING]); + dst->issuerCertificates = NULL; + dst->issuerCertificatesSize = 0; + } + if(src->specifiedLists & UA_TRUSTLISTMASKS_ISSUERCRLS) { + UA_Array_delete(dst->issuerCrls, dst->issuerCrlsSize, &UA_TYPES[UA_TYPES_BYTESTRING]); + dst->issuerCrls = NULL; + dst->issuerCrlsSize = 0; + } + return UA_TrustListDataType_add(src, dst); +} + UA_StatusCode UA_TrustListDataType_remove(const UA_TrustListDataType *src, UA_TrustListDataType *dst) { if(!dst) From 373986fbf16ce1be42f20c14d11982a9be1232de Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Wed, 15 Jan 2025 09:34:33 +0100 Subject: [PATCH 128/158] fix(plugin): Reset only the specified part of the trust list when setting it --- plugins/crypto/mbedtls/certificategroup.c | 4 ++-- plugins/crypto/openssl/certificategroup.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index da7f5611f..34c3443c9 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -94,8 +94,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD return UA_STATUSCODE_BADINTERNALERROR; } context->reloadRequired = true; - UA_TrustListDataType_clear(&context->trustList); - return UA_TrustListDataType_add(trustList, &context->trustList); + /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */ + return UA_TrustListDataType_set(trustList, &context->trustList); } static UA_StatusCode diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index 14081cfb9..a85638dd0 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -92,8 +92,8 @@ MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListD return UA_STATUSCODE_BADINTERNALERROR; } context->reloadRequired = true; - UA_TrustListDataType_clear(&context->trustList); - return UA_TrustListDataType_add(trustList, &context->trustList); + /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */ + return UA_TrustListDataType_set(trustList, &context->trustList); } static UA_StatusCode From 50f62861072e4af778f590ed7770b93e60ef5ebd Mon Sep 17 00:00:00 2001 From: Noel Graf Date: Wed, 15 Jan 2025 09:37:34 +0100 Subject: [PATCH 129/158] fix(test): Reset trust list for each test case --- tests/encryption/check_update_trustlist.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/encryption/check_update_trustlist.c b/tests/encryption/check_update_trustlist.c index 01e58c0fc..a8af604d2 100644 --- a/tests/encryption/check_update_trustlist.c +++ b/tests/encryption/check_update_trustlist.c @@ -56,6 +56,16 @@ static void setup2(void) { server = UA_Server_newForUnitTestWithSecurityPolicies_Filestore(4840, &certificate, &privateKey, storePath); + + /* Reset the trust list for each test case. + * This is necessary so that all of the old certificates + * from previous test cases are deleted from the PKI file store */ + UA_TrustListDataType trustList; + UA_TrustListDataType_init(&trustList); + trustList.specifiedLists = UA_TRUSTLISTMASKS_ALL; + server->config.secureChannelPKI.setTrustList(&server->config.secureChannelPKI, &trustList); + UA_TrustListDataType_clear(&trustList); + ck_assert(server != NULL); } #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ From 294b0278361c13e60ffa23af49dcef2a40e78d16 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 17:04:26 +0100 Subject: [PATCH 130/158] refactor(pubsub): Use the typical enum nomenclature for UA_DataSetMessageType --- include/open62541/pubsub.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/open62541/pubsub.h b/include/open62541/pubsub.h index fdec13fc7..d39488b85 100644 --- a/include/open62541/pubsub.h +++ b/include/open62541/pubsub.h @@ -37,10 +37,14 @@ typedef enum { } UA_FieldEncoding; typedef enum { - UA_DATASETMESSAGE_DATAKEYFRAME = 0, - UA_DATASETMESSAGE_DATADELTAFRAME = 1, - UA_DATASETMESSAGE_EVENT = 2, - UA_DATASETMESSAGE_KEEPALIVE = 3 + UA_DATASETMESSAGETYPE_DATAKEYFRAME = 0, + UA_DATASETMESSAGE_DATAKEYFRAME = 0, + UA_DATASETMESSAGETYPE_DATADELTAFRAME = 1, + UA_DATASETMESSAGE_DATADELTAFRAME = 1, + UA_DATASETMESSAGETYPE_EVENT = 2, + UA_DATASETMESSAGE_EVENT = 2, + UA_DATASETMESSAGETYPE_KEEPALIVE = 3, + UA_DATASETMESSAGE_KEEPALIVE = 3 } UA_DataSetMessageType; typedef struct { From a3c56dda76b204ebfea926ce876c873c102c9354 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 17:08:44 +0100 Subject: [PATCH 131/158] feat(pubsub): Check for payload variants with a NULL type --- src/pubsub/ua_pubsub_networkmessage_binary.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index 387318717..f07be2acb 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -1352,6 +1352,10 @@ UA_DataSetMessage_keyFrame_encodeBinary(const UA_DataSetMessage* src, UA_Byte ** for(UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { const UA_DataValue *v = &src->data.keyFrameData.dataSetFields[i]; + if(!v->value.type) { + rv = UA_STATUSCODE_BADINTERNALERROR; + break; + } if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { rv = UA_Variant_encodeBinary(&v->value, bufPos, bufEnd); @@ -1664,7 +1668,7 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, } else if(p->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) { if(p->data.keyFrameData.dataSetFields != NULL) { if(offsetBuffer) { - if(!v->value.type->pointerFree) + if(!v->value.type || !v->value.type->pointerFree) return 0; /* only integer types for now */ /* Count the memory size of the specific field */ offsetBuffer->rawMessageLength += v->value.type->memSize; From 3efc4344ca3b859aad2ece3bd3e9a037cfd8c566 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 17:17:03 +0100 Subject: [PATCH 132/158] refactor(pubsub): Reorder the fields in pubsub.h to make the structs more readable --- include/open62541/pubsub.h | 96 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/include/open62541/pubsub.h b/include/open62541/pubsub.h index d39488b85..b0d1bbb4c 100644 --- a/include/open62541/pubsub.h +++ b/include/open62541/pubsub.h @@ -48,21 +48,31 @@ typedef enum { } UA_DataSetMessageType; typedef struct { + /* Settings and message fields enabled with the DataSetFlags1 */ UA_Boolean dataSetMessageValid; + UA_FieldEncoding fieldEncoding; + UA_Boolean dataSetMessageSequenceNrEnabled; - UA_Boolean timestampEnabled; - UA_Boolean statusEnabled; - UA_Boolean configVersionMajorVersionEnabled; - UA_Boolean configVersionMinorVersionEnabled; - UA_DataSetMessageType dataSetMessageType; - UA_Boolean picoSecondsIncluded; UA_UInt16 dataSetMessageSequenceNr; - UA_UtcTime timestamp; - UA_UInt16 picoSeconds; + + UA_Boolean statusEnabled; UA_UInt16 status; + + UA_Boolean configVersionMajorVersionEnabled; UA_UInt32 configVersionMajorVersion; + + UA_Boolean configVersionMinorVersionEnabled; UA_UInt32 configVersionMinorVersion; + + /* Settings and message fields enabled with the DataSetFlags2 */ + UA_DataSetMessageType dataSetMessageType; + + UA_Boolean timestampEnabled; + UA_UtcTime timestamp; + + UA_Boolean picoSecondsIncluded; + UA_UInt16 picoSeconds; } UA_DataSetMessageHeader; typedef struct { @@ -108,12 +118,15 @@ typedef enum { typedef struct { UA_Boolean writerGroupIdEnabled; - UA_Boolean groupVersionEnabled; - UA_Boolean networkMessageNumberEnabled; - UA_Boolean sequenceNumberEnabled; UA_UInt16 writerGroupId; + + UA_Boolean groupVersionEnabled; UA_UInt32 groupVersion; + + UA_Boolean networkMessageNumberEnabled; UA_UInt16 networkMessageNumber; + + UA_Boolean sequenceNumberEnabled; UA_UInt16 sequenceNumber; } UA_NetworkMessageGroupHeader; @@ -121,41 +134,58 @@ typedef struct { typedef struct { UA_Boolean networkMessageSigned; + UA_Boolean networkMessageEncrypted; + UA_Boolean securityFooterEnabled; - UA_Boolean forceKeyReset; - UA_UInt32 securityTokenId; - UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH]; - UA_UInt16 messageNonceSize; UA_UInt16 securityFooterSize; + + UA_Boolean forceKeyReset; + + UA_UInt32 securityTokenId; + + UA_UInt16 messageNonceSize; + UA_Byte messageNonce[UA_NETWORKMESSAGE_MAX_NONCE_LENGTH]; } UA_NetworkMessageSecurityHeader; typedef struct { UA_Byte version; - UA_Boolean messageIdEnabled; - UA_String messageId; /* For Json NetworkMessage */ - UA_Boolean publisherIdEnabled; - UA_Boolean groupHeaderEnabled; - UA_Boolean payloadHeaderEnabled; - UA_Boolean dataSetClassIdEnabled; - UA_Boolean securityEnabled; - UA_Boolean timestampEnabled; - UA_Boolean picosecondsEnabled; - UA_Boolean chunkMessage; - UA_Boolean promotedFieldsEnabled; - UA_NetworkMessageType networkMessageType; - UA_PublisherId publisherId; - UA_Guid dataSetClassId; + /* Fields defined via the UADPFlags */ + UA_Boolean publisherIdEnabled; + UA_PublisherId publisherId; + + UA_Boolean groupHeaderEnabled; UA_NetworkMessageGroupHeader groupHeader; - UA_DateTime timestamp; - UA_UInt16 picoseconds; - UA_UInt16 promotedFieldsSize; - UA_Variant* promotedFields; /* BaseDataType */ + UA_Boolean payloadHeaderEnabled; + /* Fields defined via the Extended1Flags */ + UA_Boolean dataSetClassIdEnabled; + UA_Guid dataSetClassId; + + UA_Boolean securityEnabled; UA_NetworkMessageSecurityHeader securityHeader; + UA_Boolean timestampEnabled; + UA_DateTime timestamp; + + UA_Boolean picosecondsEnabled; + UA_UInt16 picoseconds; + + /* Fields defined via the Extended2Flags */ + UA_Boolean chunkMessage; + + UA_Boolean promotedFieldsEnabled; + UA_UInt16 promotedFieldsSize; + UA_Variant *promotedFields; /* BaseDataType */ + + UA_NetworkMessageType networkMessageType; + + /* For Json NetworkMessage */ + UA_Boolean messageIdEnabled; + UA_String messageId; + union { struct { UA_DataSetMessage *dataSetMessages; From 327474fdcf75b95e0609dc4dcc165348fc87e25a Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 17:42:53 +0100 Subject: [PATCH 133/158] feat(pubsub): Add more fields to the generated NetworkMessage offsets --- src/pubsub/ua_pubsub_networkmessage.h | 9 ++- src/pubsub/ua_pubsub_networkmessage_binary.c | 63 ++++++++++++++++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index 4fd42d69c..85c96b654 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -28,11 +28,14 @@ _UA_BEGIN_DECLS /* Offsets for buffered messages in the PubSub fast path. */ typedef enum { UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP, + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS, + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS, UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER, UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_FIELDENCDODING, - UA_PUBSUB_OFFSETTYPE_TIMESTAMP_PICOSECONDS, - UA_PUBSUB_OFFSETTYPE_TIMESTAMP, /* source pointer */ - UA_PUBSUB_OFFSETTYPE_TIMESTAMP_NOW, /* no source */ + UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP, + UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS, + UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION, UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE, UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL, UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT, diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index f07be2acb..df75175a9 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -157,9 +157,24 @@ UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffe case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: rv = DECODE_BINARY(&nm->groupHeader.sequenceNumber, UINT16); break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: + rv = DECODE_BINARY(&nm->timestamp, DATETIME); + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: + rv = DECODE_BINARY(&nm->picoseconds, UINT16); + break; case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: rv = DECODE_BINARY(&dsm->header.dataSetMessageSequenceNr, UINT16); break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: + rv = DECODE_BINARY(&dsm->header.status, UINT16); + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: + rv = DECODE_BINARY(&dsm->header.timestamp, DATETIME); + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: + rv = DECODE_BINARY(&dsm->header.picoSeconds, UINT16); + break; case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: UA_DataValue_clear(&dsm->data.keyFrameData.dataSetFields[payloadCounter]); rv = DECODE_BINARY(&dsm->data.keyFrameData.dataSetFields[payloadCounter], DATAVALUE); @@ -976,8 +991,17 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( size += 2; /* UA_UInt16_calcSizeBinary(&p->groupHeader.writerGroupId) */ } - if(p->groupHeader.groupVersionEnabled) + if(p->groupHeader.groupVersionEnabled) { + if(offsetBuffer) { + size_t pos = offsetBuffer->offsetsSize; + if(!increaseOffsetArray(offsetBuffer)) + return 0; + offsetBuffer->offsets[pos].offset = size; + offsetBuffer->offsets[pos].contentType = + UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; + } size += 4; /* UA_UInt32_calcSizeBinary(&p->groupHeader.groupVersion) */ + } if(p->groupHeader.networkMessageNumberEnabled) { size += 2; /* UA_UInt16_calcSizeBinary(&p->groupHeader.networkMessageNumber) */ @@ -1019,7 +1043,7 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(!increaseOffsetArray(offsetBuffer)) return 0; offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_TIMESTAMP; + offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; } size += 8; /* UA_DateTime_calcSizeBinary(&p->timestamp) */ } @@ -1030,7 +1054,7 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(!increaseOffsetArray(offsetBuffer)) return 0; offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_TIMESTAMP_PICOSECONDS; + offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; } size += 2; /* UA_UInt16_calcSizeBinary(&p->picoseconds) */ } @@ -1625,14 +1649,41 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, size += 2; /* UA_UInt16_calcSizeBinary(&p->header.dataSetMessageSequenceNr) */ } - if(p->header.timestampEnabled) + if(p->header.timestampEnabled) { + if(offsetBuffer) { + size_t pos = offsetBuffer->offsetsSize; + if(!increaseOffsetArray(offsetBuffer)) + return 0; + offsetBuffer->offsets[pos].offset = size; + offsetBuffer->offsets[pos].contentType = + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP; + } size += 8; /* UA_DateTime_calcSizeBinary(&p->header.timestamp) */ + } - if(p->header.picoSecondsIncluded) + if(p->header.picoSecondsIncluded) { + if(offsetBuffer) { + size_t pos = offsetBuffer->offsetsSize; + if(!increaseOffsetArray(offsetBuffer)) + return 0; + offsetBuffer->offsets[pos].offset = size; + offsetBuffer->offsets[pos].contentType = + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS; + } size += 2; /* UA_UInt16_calcSizeBinary(&p->header.picoSeconds) */ + } - if(p->header.statusEnabled) + if(p->header.statusEnabled) { + if(offsetBuffer) { + size_t pos = offsetBuffer->offsetsSize; + if(!increaseOffsetArray(offsetBuffer)) + return 0; + offsetBuffer->offsets[pos].offset = size; + offsetBuffer->offsets[pos].contentType = + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS; + } size += 2; /* UA_UInt16_calcSizeBinary(&p->header.status) */ + } if(p->header.configVersionMajorVersionEnabled) size += 4; /* UA_UInt32_calcSizeBinary(&p->header.configVersionMajorVersion) */ From cba40d669f12a7d96b2f6c24bd6ec4a7e10e1080 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 17:46:44 +0100 Subject: [PATCH 134/158] feat(pubsub): Offset table generation for PubSub messages --- include/open62541/server_pubsub.h | 61 +++ src/pubsub/ua_pubsub_networkmessage.h | 1 + src/pubsub/ua_pubsub_networkmessage_binary.c | 10 + src/pubsub/ua_pubsub_readergroup.c | 393 +++++++++++++++++++ src/pubsub/ua_pubsub_writergroup.c | 180 +++++++++ tests/CMakeLists.txt | 1 + tests/pubsub/check_pubsub_offset.c | 370 +++++++++++++++++ 7 files changed, 1016 insertions(+) create mode 100644 tests/pubsub/check_pubsub_offset.c diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index d49070626..244151a08 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -1021,6 +1021,67 @@ UA_Server_setWriterGroupActivateKey(UA_Server *server, #endif /* UA_ENABLE_PUBSUB_SKS */ +/** + * Offset Table + * ------------ + * When the content of a PubSub Networkmessage has a fixed length, then only a + * few "content bytes" at known locations within the NetworkMessage change + * between publish cycles. The so-called offset table exposes this to enable + * fast-path implementations for realtime applications. */ + +typedef enum { + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION, /* UInt32 */ + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER, /* UInt16 */ + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP, /* DateTime */ + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS, /* UInt16 */ + UA_PUBSUBOFFSETTYPE_DATASETMESSAGE, /* no content, marks the DSM beginning */ + UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, /* UInt16 */ + UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS, /* UInt16 */ + UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP, /* DateTime */ + UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS, /* UInt16 */ + UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE, + UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT, + UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW +} UA_PubSubOffsetType; + +typedef struct { + UA_PubSubOffsetType offsetType; /* Content type at the offset */ + size_t offset; /* Offset in the NetworkMessage */ + + /* The PubSub component that originates / receives the offset content. + * - For NetworkMessage-offsets this is the ReaderGroup / WriterGroup. + * - For DataSetMessage-offsets this is DataSetReader / DataSetWriter. + * - For DataSetFields this is the NodeId associated with the field: + * - For Writers the NodeId of the DataSetField (in a PublishedDataSet). + * - For Readers the TargetNodeId of the FieldTargetDataType (this can + * come from a SubscribedDataSet or a StandaloneSubscribedDataSets). + * Access more metadata from the FieldTargetVariable by counting the + * index of the current DataSetField-offset within the DataSetMessage + * and use that index for the lookup in the DataSetReader configuration. */ + UA_NodeId component; +} UA_PubSubOffset; + +typedef struct { + UA_PubSubOffset *offsets; /* Array of offset entries */ + size_t offsetsSize; /* Number of entries */ + UA_ByteString networkMessage; /* Current NetworkMessage in binary encoding */ +} UA_PubSubOffsetTable; + +UA_EXPORT void +UA_PubSubOffsetTable_clear(UA_PubSubOffsetTable *ot); + +/* Compute the offset table for a WriterGroup */ +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Server_computeWriterGroupOffsetTable(UA_Server *server, + const UA_NodeId writerGroupId, + UA_PubSubOffsetTable *ot); + +/* Compute the offset table for a ReaderGroup */ +UA_EXPORT UA_StatusCode UA_THREADSAFE +UA_Server_computeReaderGroupOffsetTable(UA_Server *server, + const UA_NodeId readerGroupId, + UA_PubSubOffsetTable *ot); + #endif /* UA_ENABLE_PUBSUB */ _UA_END_DECLS diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index 85c96b654..f18b355a7 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -27,6 +27,7 @@ _UA_BEGIN_DECLS /* Offsets for buffered messages in the PubSub fast path. */ typedef enum { + UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE, /* no content, marks the beginning of the DSM */ UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP, UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS, diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index df75175a9..90e33d7aa 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -195,6 +195,8 @@ UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffe } payloadCounter++; break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: + break; /* Nothing to do */ default: return UA_STATUSCODE_BADNOTSUPPORTED; } @@ -1082,6 +1084,14 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->payloadHeaderEnabled && count > 1) size += (size_t)(2LU * count); /* DataSetMessagesSize (uint16) */ for(size_t i = 0; i < count; i++) { + if(offsetBuffer) { + size_t pos = offsetBuffer->offsetsSize; + if(!increaseOffsetArray(offsetBuffer)) + return 0; + offsetBuffer->offsets[pos].offset = size; + offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE; + } + /* size = ... as the original size is used as the starting point in * UA_DataSetMessage_calcSizeBinary */ UA_DataSetMessage *dsm = &p->payload.dataSetPayload.dataSetMessages[i]; diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index fc501f7b2..e2b590508 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -1177,4 +1177,397 @@ UA_Server_setReaderGroupEncryptionKeys(UA_Server *server, return res; } +static UA_StatusCode +UA_PubSubDataSetReader_generateKeyFrameMessage(UA_Server *server, + UA_DataSetMessage *dsm, + UA_DataSetReader *dsr) { + /* Prepare DataSetMessageContent */ + UA_TargetVariables *tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget; + dsm->header.dataSetMessageValid = true; + dsm->header.dataSetMessageType = UA_DATASETMESSAGE_DATAKEYFRAME; + dsm->data.keyFrameData.fieldCount = (UA_UInt16) tv->targetVariablesSize; + dsm->data.keyFrameData.dataSetFields = (UA_DataValue *) + UA_Array_new(tv->targetVariablesSize, &UA_TYPES[UA_TYPES_DATAVALUE]); + if(!dsm->data.keyFrameData.dataSetFields) + return UA_STATUSCODE_BADOUTOFMEMORY; + + dsm->data.keyFrameData.dataSetMetaDataType = + &dsr->config.dataSetMetaData; + + for(size_t counter = 0; counter < tv->targetVariablesSize; counter++) { + /* Sample the value and set the source in the reader config */ + UA_DataValue *dfv = &dsm->data.keyFrameData.dataSetFields[counter]; + UA_FieldTargetVariable *ftv = &tv->targetVariables[counter]; + + UA_ReadValueId rvi; + UA_ReadValueId_init(&rvi); + rvi.nodeId = ftv->targetVariable.targetNodeId; + rvi.attributeId = ftv->targetVariable.attributeId; + rvi.indexRange = ftv->targetVariable.writeIndexRange; + + *dfv = readWithSession(server, &server->adminSession, &rvi, + UA_TIMESTAMPSTORETURN_NEITHER); + + /* Deactivate statuscode? */ + if(((u64)dsr->config.dataSetFieldContentMask & + (u64)UA_DATASETFIELDCONTENTMASK_STATUSCODE) == 0) + dfv->hasStatus = false; + + /* Deactivate timestamps */ + if(((u64)dsr->config.dataSetFieldContentMask & + (u64)UA_DATASETFIELDCONTENTMASK_SOURCETIMESTAMP) == 0) + dfv->hasSourceTimestamp = false; + if(((u64)dsr->config.dataSetFieldContentMask & + (u64)UA_DATASETFIELDCONTENTMASK_SOURCEPICOSECONDS) == 0) + dfv->hasSourcePicoseconds = false; + if(((u64)dsr->config.dataSetFieldContentMask & + (u64)UA_DATASETFIELDCONTENTMASK_SERVERTIMESTAMP) == 0) + dfv->hasServerTimestamp = false; + if(((u64)dsr->config.dataSetFieldContentMask & + (u64)UA_DATASETFIELDCONTENTMASK_SERVERPICOSECONDS) == 0) + dfv->hasServerPicoseconds = false; + } + + return UA_STATUSCODE_GOOD; +} + +/* Generate a DataSetMessage for the given reader. */ +static UA_StatusCode +UA_DataSetReader_generateDataSetMessage(UA_Server *server, + UA_DataSetMessage *dsm, + UA_DataSetReader *dsr) { + /* Support only for UADP configuration + * TODO: JSON encoding if UA_DataSetReader_generateDataSetMessage used other + * that RT configuration */ + + dsm->dataSetWriterId = dsr->config.dataSetWriterId; + + UA_ExtensionObject *settings = &dsr->config.messageSettings; + if(settings->content.decoded.type != &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]) + return UA_STATUSCODE_BADNOTSUPPORTED; + + /* The configuration Flags are included inside the std. defined + * UA_UadpDataSetReaderMessageDataType */ + UA_UadpDataSetReaderMessageDataType defaultUadpConfiguration; + UA_UadpDataSetReaderMessageDataType *dsrMessageDataType = + (UA_UadpDataSetReaderMessageDataType*) settings->content.decoded.data; + + if(!(settings->encoding == UA_EXTENSIONOBJECT_DECODED || + settings->encoding == UA_EXTENSIONOBJECT_DECODED_NODELETE) || + !dsrMessageDataType->dataSetMessageContentMask) { + /* Create default flag configuration if no dataSetMessageContentMask or + * even messageSettings in UadpDataSetWriterMessageDataType was + * passed. */ + memset(&defaultUadpConfiguration, 0, sizeof(UA_UadpDataSetReaderMessageDataType)); + defaultUadpConfiguration.dataSetMessageContentMask = (UA_UadpDataSetMessageContentMask) + ((u64)UA_UADPDATASETMESSAGECONTENTMASK_TIMESTAMP | + (u64)UA_UADPDATASETMESSAGECONTENTMASK_MAJORVERSION | + (u64)UA_UADPDATASETMESSAGECONTENTMASK_MINORVERSION); + dsrMessageDataType = &defaultUadpConfiguration; + } + + /* Sanity-test the configuration */ + if(dsrMessageDataType && + (dsrMessageDataType->networkMessageNumber != 0 || + dsrMessageDataType->dataSetOffset != 0)) { + dsrMessageDataType->networkMessageNumber = 0; + dsrMessageDataType->dataSetOffset = 0; + } + + /* The field encoding depends on the flags inside the reader config. */ + if(dsr->config.dataSetFieldContentMask & (u64)UA_DATASETFIELDCONTENTMASK_RAWDATA) { + dsm->header.fieldEncoding = UA_FIELDENCODING_RAWDATA; + } else if((u64)dsr->config.dataSetFieldContentMask & + ((u64)UA_DATASETFIELDCONTENTMASK_SOURCETIMESTAMP | + (u64)UA_DATASETFIELDCONTENTMASK_SERVERPICOSECONDS | + (u64)UA_DATASETFIELDCONTENTMASK_SOURCEPICOSECONDS | + (u64)UA_DATASETFIELDCONTENTMASK_STATUSCODE)) { + dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; + } else { + dsm->header.fieldEncoding = UA_FIELDENCODING_VARIANT; + } + + /* Std: 'The DataSetMessageContentMask defines the flags for the content + * of the DataSetMessage header.' */ + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_MAJORVERSION) { + dsm->header.configVersionMajorVersionEnabled = true; + dsm->header.configVersionMajorVersion = + dsr->config.dataSetMetaData.configurationVersion.majorVersion; + } + + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_MINORVERSION) { + dsm->header.configVersionMinorVersionEnabled = true; + dsm->header.configVersionMinorVersion = + dsr->config.dataSetMetaData.configurationVersion.minorVersion; + } + + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER) { + /* Will be modified when subscriber receives new nw msg */ + dsm->header.dataSetMessageSequenceNrEnabled = true; + dsm->header.dataSetMessageSequenceNr = 1; + } + + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_TIMESTAMP) { + dsm->header.timestampEnabled = true; + dsm->header.timestamp = UA_DateTime_now(); + } + + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_PICOSECONDS) + dsm->header.picoSecondsIncluded = false; + + if((u64)dsrMessageDataType->dataSetMessageContentMask & + (u64)UA_UADPDATASETMESSAGECONTENTMASK_STATUS) + dsm->header.statusEnabled = true; + + /* Not supported for Delta frames atm */ + return UA_PubSubDataSetReader_generateKeyFrameMessage(server, dsm, dsr); +} + +static UA_StatusCode +readerGroupGenerateNetworkMessage(UA_ReaderGroup *wg, UA_DataSetReader **dsr, + UA_DataSetMessage *dsm, UA_Byte dsmCount, + UA_ExtensionObject *messageSettings, + UA_NetworkMessage *nm) { + if(messageSettings->content.decoded.type != &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]) + return UA_STATUSCODE_BADNOTSUPPORTED; + + /* Set the header flags */ + UA_UadpDataSetReaderMessageDataType *dsrm = + (UA_UadpDataSetReaderMessageDataType*)messageSettings->content.decoded.data; + nm->publisherIdEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID) != 0; + nm->groupHeaderEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER) != 0; + nm->groupHeader.writerGroupIdEnabled = + ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID) != 0; + nm->groupHeader.groupVersionEnabled = + ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPVERSION) != 0; + nm->groupHeader.networkMessageNumberEnabled = + ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_NETWORKMESSAGENUMBER) != 0; + nm->groupHeader.sequenceNumberEnabled = + ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER) != 0; + nm->payloadHeaderEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER) != 0; + nm->timestampEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_TIMESTAMP) != 0; + nm->picosecondsEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_PICOSECONDS) != 0; + nm->dataSetClassIdEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_DATASETCLASSID) != 0; + nm->promotedFieldsEnabled = ((u64)dsrm->networkMessageContentMask & + (u64)UA_UADPNETWORKMESSAGECONTENTMASK_PROMOTEDFIELDS) != 0; + + /* Set the NetworkMessage header */ + nm->version = 1; + nm->networkMessageType = UA_NETWORKMESSAGE_DATASET; + nm->publisherId = dsr[0]->config.publisherId; + + /* Set the group header (use default sequence numbers) */ + nm->groupHeader.networkMessageNumber = 1; + nm->groupHeader.sequenceNumber = 1; + nm->groupHeader.groupVersion = dsrm->groupVersion; + nm->groupHeader.writerGroupId = dsr[0]->config.writerGroupId; + + /* TODO Security Header */ + + /* Set the payload information from the dsm */ + nm->payload.dataSetPayload.dataSetMessages = dsm; + nm->payload.dataSetPayload.dataSetMessagesSize = dsmCount; + + return UA_STATUSCODE_GOOD; +} + +UA_StatusCode +UA_Server_computeReaderGroupOffsetTable(UA_Server *server, + const UA_NodeId readerGroupId, + UA_PubSubOffsetTable *ot) { + if(!server || !ot) + return UA_STATUSCODE_BADINVALIDARGUMENT; + + UA_LOCK(&server->serviceMutex); + + /* Get the ReaderGroup */ + UA_PubSubManager *psm = getPSM(server); + UA_ReaderGroup *rg = (psm) ? UA_ReaderGroup_find(psm, readerGroupId) : NULL; + if(!rg) { + UA_UNLOCK(&server->serviceMutex); + return UA_STATUSCODE_BADNOTFOUND; + } + + memset(ot, 0, sizeof(UA_PubSubOffsetTable)); + + /* Define variables here to allow the goto cleanup later on */ + size_t newo = 0; + size_t msgSize; + size_t fieldindex = 0; + UA_FieldTargetDataType *tv = NULL; + + UA_NetworkMessageOffsetBuffer oldOffsetTable; + memset(&oldOffsetTable, 0, sizeof(UA_NetworkMessageOffsetBuffer)); + + UA_NetworkMessage networkMessage; + memset(&networkMessage, 0, sizeof(UA_NetworkMessage)); + + UA_STACKARRAY(UA_DataSetMessage, dsmStore, rg->readersCount); + UA_STACKARRAY(UA_DataSetReader *, dsrStore, rg->readersCount); + memset(dsmStore, 0, sizeof(UA_DataSetMessage) * rg->readersCount); + + size_t dsmCount = 0; + UA_DataSetReader *dsr; + UA_StatusCode res = UA_STATUSCODE_GOOD; + LIST_FOREACH(dsr, &rg->readers, listEntry) { + dsrStore[dsmCount] = dsr; + res = UA_DataSetReader_generateDataSetMessage(server, &dsmStore[dsmCount], dsr); + dsmCount++; + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + } + + /* Generate the NetworkMessage */ + dsr = LIST_FIRST(&rg->readers); + res = readerGroupGenerateNetworkMessage(rg, dsrStore, dsmStore, (UA_Byte) dsmCount, + &dsr->config.messageSettings, &networkMessage); + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + + /* Compute the message length and generate the old format offset-table (done + * inside calcSizeBinary) */ + msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(&networkMessage, + &oldOffsetTable); + if(msgSize == 0) { + res = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + /* Create the encoded network message */ + res = UA_NetworkMessage_encodeBinary(&networkMessage, &ot->networkMessage); + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + + /* Allocate memory for the output offset table */ + ot->offsets = (UA_PubSubOffset*) + UA_calloc(oldOffsetTable.offsetsSize, sizeof(UA_PubSubOffset)); + if(!ot->offsets) { + res = UA_STATUSCODE_BADOUTOFMEMORY; + goto cleanup; + } + + /* Walk over the (old) offset table and convert to the new schema. + * In parallel, pick up the component NodeIds. */ + dsr = NULL; + for(size_t oldo = 0; oldo < oldOffsetTable.offsetsSize; oldo++) { + switch(oldOffsetTable.offsets[oldo].contentType) { + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: + dsr = (dsr == NULL) ? LIST_FIRST(&rg->readers) : LIST_NEXT(dsr, listEntry); + fieldindex = 0; + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL: + tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. + targetVariables[fieldindex].targetVariable; + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + fieldindex++; + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL: + tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. + targetVariables[fieldindex].targetVariable; + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + fieldindex++; + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: + tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. + targetVariables[fieldindex].targetVariable; + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + fieldindex++; + newo++; + break; + default: + break; + } + } + + ot->offsetsSize = newo; + + cleanup: + /* Clean up and return */ + UA_NetworkMessageOffsetBuffer_clear(&oldOffsetTable); + for(size_t i = 0; i < dsmCount; i++) { + UA_DataSetMessage_clear(&dsmStore[i]); + } + + UA_UNLOCK(&server->serviceMutex); + return res; +} + #endif /* UA_ENABLE_PUBSUB */ diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index e4316338e..aeacd9543 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -1666,4 +1666,184 @@ UA_Server_setWriterGroupEncryptionKeys(UA_Server *server, const UA_NodeId writer return res; } +UA_StatusCode +UA_Server_computeWriterGroupOffsetTable(UA_Server *server, + const UA_NodeId writerGroupId, + UA_PubSubOffsetTable *ot) { + /* Get the Writer Group */ + UA_PubSubManager *psm = getPSM(server); + UA_WriterGroup *wg = (psm) ? UA_WriterGroup_find(psm, writerGroupId) : NULL; + if(!wg) + return UA_STATUSCODE_BADNOTFOUND; + + /* Initialize variables so we can goto cleanup below */ + memset(ot, 0, sizeof(UA_PubSubOffsetTable)); + + UA_NetworkMessage networkMessage; + memset(&networkMessage, 0, sizeof(networkMessage)); + + UA_NetworkMessageOffsetBuffer oldOffsetTable; + memset(&oldOffsetTable, 0, sizeof(UA_NetworkMessageOffsetBuffer)); + + /* Validate the DataSetWriters and generate their DataSetMessage */ + size_t msgSize; + size_t dsmCount = 0; + UA_DataSetWriter *dsw; + UA_StatusCode res = UA_STATUSCODE_GOOD; + UA_STACKARRAY(UA_UInt16, dsWriterIds, wg->writersCount); + UA_STACKARRAY(UA_DataSetMessage, dsmStore, wg->writersCount); + memset(dsmStore, 0, sizeof(UA_DataSetMessage) * wg->writersCount); + LIST_FOREACH(dsw, &wg->writers, listEntry) { + dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; + res = UA_DataSetWriter_prepareDataSet(psm, dsw, &dsmStore[dsmCount]); + dsmCount++; + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + } + + /* Generate the NetworkMessage */ + res = generateNetworkMessage(wg->linkedConnection, wg, dsmStore, dsWriterIds, + (UA_Byte) dsmCount, &wg->config.messageSettings, + &wg->config.transportSettings, &networkMessage); + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + + /* Compute the message length and generate the old format offset-table (done + * inside calcSizeBinary) */ + msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(&networkMessage, + &oldOffsetTable); + if(msgSize == 0) { + res = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + /* Create the encoded network message */ + res = UA_NetworkMessage_encodeBinary(&networkMessage, &ot->networkMessage); + if(res != UA_STATUSCODE_GOOD) + goto cleanup; + + /* Allocate memory for the output offset table */ + ot->offsets = (UA_PubSubOffset*) + UA_calloc(oldOffsetTable.offsetsSize, sizeof(UA_PubSubOffset)); + if(!ot->offsets) { + res = UA_STATUSCODE_BADOUTOFMEMORY; + goto cleanup; + } + + /* Walk over the (old) offset table and convert to the new schema. + * In parallel, pick up the component NodeIds. */ + dsw = NULL; + size_t newo = 0; + UA_DataSetField *field = NULL; + for(size_t oldo = 0; oldo < oldOffsetTable.offsetsSize; oldo++) { + switch(oldOffsetTable.offsets[oldo].contentType) { + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: + dsw = (dsw == NULL) ? LIST_FIRST(&wg->writers) : LIST_NEXT(dsw, listEntry); + field = NULL; + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL: + field = (field == NULL) ? + TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL: + field = (field == NULL) ? + TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); + newo++; + break; + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: + case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: + field = (field == NULL) ? + TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); + ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; + ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; + UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); + newo++; + break; + default: + break; + } + } + + ot->offsetsSize = newo; + + /* Clean up */ + cleanup: + UA_NetworkMessageOffsetBuffer_clear(&oldOffsetTable); + for(size_t i = 0; i < dsmCount; i++) { + UA_DataSetMessage_clear(&dsmStore[i]); + } + return res; +} + +void +UA_PubSubOffsetTable_clear(UA_PubSubOffsetTable *ot) { + for(size_t i = 0; i < ot->offsetsSize; i++) { + UA_NodeId_clear(&ot->offsets[i].component); + } + UA_ByteString_clear(&ot->networkMessage); + UA_free(ot->offsets); + memset(ot, 0, sizeof(UA_PubSubOffsetTable)); +} + #endif /* UA_ENABLE_PUBSUB */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f401eeef..711ca70ab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -277,6 +277,7 @@ if(UA_ENABLE_PUBSUB) ua_add_test(pubsub/check_pubsub_subscribe_rt_levels.c) ua_add_test(pubsub/check_pubsub_multiple_subscribe_rt_levels.c) + ua_add_test(pubsub/check_pubsub_offset.c) if(UA_ARCHITECTURE_POSIX) ua_add_test(pubsub/check_pubsub_custom_state_machine.c) endif() diff --git a/tests/pubsub/check_pubsub_offset.c b/tests/pubsub/check_pubsub_offset.c new file mode 100644 index 000000000..ae6067d43 --- /dev/null +++ b/tests/pubsub/check_pubsub_offset.c @@ -0,0 +1,370 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2014 Fraunhofer IOSB (Author: Julius Pfrommer) + */ + +#include +#include + +#include "test_helpers.h" +#include "testing_clock.h" + +#include +#include + +#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100 +#define PUBSUB_CONFIG_FIELD_COUNT 10 + +UA_Server *server; + +static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; +static UA_NodeId readerGroupIdentifier, readerIdentifier; + +/* Values in static locations. We cycle the dvPointers double-pointer to the + * next with atomic operations. */ +UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT]; + +UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; + +UA_DataSetReaderConfig readerConfig; + +static UA_NetworkAddressUrlDataType networkAddressUrl = + {{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")}; +static UA_String transportProfile = + UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + +static void setup(void) { + server = UA_Server_newForUnitTest(); + ck_assert(server != NULL); + UA_Server_run_startup(server); +} + +static void teardown(void) { + UA_Server_run_shutdown(server); + UA_Server_delete(server); +} + +START_TEST(PublisherOffsets) { + /* Prepare the values */ + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + valueStore[i] = (UA_UInt32) i + 1; + UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]); + dvStore[i].hasValue = true; + dvPointers[i] = &dvStore[i]; + } + + /* Add a PubSubConnection */ + UA_PubSubConnectionConfig connectionConfig; + memset(&connectionConfig, 0, sizeof(connectionConfig)); + connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); + connectionConfig.transportProfileUri = + UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + UA_NetworkAddressUrlDataType networkAddressUrl = + {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + connectionConfig.publisherId.id.uint16 = 2234; + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); + + /* Add a PublishedDataSet */ + UA_PublishedDataSetConfig publishedDataSetConfig; + memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); + publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; + publishedDataSetConfig.name = UA_STRING("Demo PDS"); + UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); + + /* Add DataSetFields with static value source to PDS */ + UA_DataSetFieldConfig dsfConfig; + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + /* TODO: Point to a variable in the information model */ + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; + dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; + UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); + } + + /* Add a WriterGroup */ + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + + /* Change message settings of writerGroup to send PublisherId, WriterGroupId + * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ + UA_UadpWriterGroupMessageDataType writerGroupMessage; + UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); + writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); + + UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent); + + /* Add a DataSetWriter to the WriterGroup */ + UA_NodeId dataSetWriterIdent; + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + dataSetWriterConfig.keyFrameCount = 10; + dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType; + UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType); + uadpDataSetWriterMessageDataType.dataSetMessageContentMask = + UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings, + &uadpDataSetWriterMessageDataType, + &UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]); + + UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, + &dataSetWriterConfig, &dataSetWriterIdent); + + UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, + &dataSetWriterConfig, &dataSetWriterIdent); + + /* Print the Offset Table */ + UA_PubSubOffsetTable ot; + UA_Server_computeWriterGroupOffsetTable(server, writerGroupIdent, &ot); + for(size_t i = 0; i < ot.offsetsSize; i++) { + UA_String out = UA_STRING_NULL; + UA_NodeId_print(&ot.offsets[i].component, &out); + printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n", + (unsigned)i, (unsigned)ot.offsets[i].offset, + (unsigned)ot.offsets[i].offsetType, + (int)out.length, out.data); + UA_String_clear(&out); + } + + /* Cleanup */ + UA_PubSubOffsetTable_clear(&ot); +} END_TEST + +/* Define MetaData for TargetVariables */ +static void +fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { + if(pMetaData == NULL) + return; + + UA_DataSetMetaDataType_init (pMetaData); + pMetaData->name = UA_STRING ("DataSet 1"); + + /* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT + * to create targetVariables */ + pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + + for(size_t i = 0; i < pMetaData->fieldsSize; i++) { + /* UInt32 DataType */ + UA_FieldMetaData_init (&pMetaData->fields[i]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[i].dataType); + pMetaData->fields[i].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[i].name = UA_STRING ("UInt32 varibale"); + pMetaData->fields[i].valueRank = -1; /* scalar */ + } +} + +/* Add new connection to the server */ +static void +addPubSubConnection(UA_Server *server) { + /* Configuration creation for the connection */ + UA_PubSubConnectionConfig connectionConfig; + memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); + connectionConfig.name = UA_STRING("UDPMC Connection 1"); + connectionConfig.transportProfileUri = transportProfile; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; + connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); +} + +/* Add ReaderGroup to the created connection */ +static void +addReaderGroup(UA_Server *server) { + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING("ReaderGroup1"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; + UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); +} + +/* Set SubscribedDataSet type to TargetVariables data type + * Add subscribedvariables to the DataSetReader */ +static void +addSubscribedVariables (UA_Server *server) { + UA_NodeId folderId; + UA_NodeId newnodeId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if(folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER), + UA_NS0ID(ORGANIZES), folderBrowseName, + UA_NS0ID(BASEOBJECTTYPE), oAttr, + NULL, &folderId); + + /* Set the subscribed data to TargetVariable type */ + readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; + /* Create the TargetVariables with respect to DataSetMetaData fields */ + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = + readerConfig.dataSetMetaData.fieldsSize; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = + (UA_FieldTargetVariable *)UA_calloc( + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, + sizeof(UA_FieldTargetVariable)); + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + /* Variable to subscribe data */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + // Initialize the values at first to create the buffered NetworkMessage + // with correct size and offsets + UA_Variant value; + UA_Variant_init(&value); + UA_UInt32 intValue = 0; + UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]); + vAttr.value = value; + UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), + folderId, UA_NS0ID(HASCOMPONENT), + UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NS0ID(BASEDATAVARIABLETYPE), + vAttr, NULL, &newnodeId); + repeatedFieldValues[i] = 0; + repeatedDataValueRT[i] = UA_DataValue_new(); + UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], + &UA_TYPES[UA_TYPES_UINT32]); + repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; + repeatedDataValueRT[i]->hasValue = true; + + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + memset(&valueBackend, 0, sizeof(UA_ValueBackend)); + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &repeatedDataValueRT[i]; + UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); + + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + + /* For creating Targetvariables */ + UA_FieldTargetDataType_init(ftdt); + ftdt->attributeId = UA_ATTRIBUTEID_VALUE; + ftdt->targetNodeId = newnodeId; + } +} + +/* Add DataSetReader to the ReaderGroup */ +static void +addDataSetReader(UA_Server *server) { + memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING("DataSet Reader 1"); + /* Parameters to filter which DataSetMessage has to be processed + * by the DataSetReader */ + /* The following parameters are used to show that the data published by + * tutorial_pubsub_publish.c is being subscribed and is being updated in + * the information model */ + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + readerConfig.publisherId.id.uint16 = publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW; + readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + + readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + /* Setting up Meta data configuration in DataSetReader */ + fillTestDataSetMetaData(&readerConfig.dataSetMetaData); + + addSubscribedVariables(server); + UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); + + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + UA_FieldTargetDataType_clear(ftdt); + } + + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); +} + +START_TEST(SubscriberOffsets) { + addPubSubConnection(server); + addReaderGroup(server); + addDataSetReader(server); + + /* Print the Offset Table */ + UA_PubSubOffsetTable ot; + UA_Server_computeReaderGroupOffsetTable(server, readerGroupIdentifier, &ot); + for(size_t i = 0; i < ot.offsetsSize; i++) { + UA_String out = UA_STRING_NULL; + UA_NodeId_print(&ot.offsets[i].component, &out); + printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n", + (unsigned)i, (unsigned)ot.offsets[i].offset, + (unsigned)ot.offsets[i].offsetType, + (int)out.length, out.data); + UA_String_clear(&out); + } + + UA_PubSubOffsetTable_clear(&ot); + for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + UA_DataValue_delete(repeatedDataValueRT[i]); + } +} END_TEST + +int main(void) { + TCase *tc_offset = tcase_create("PubSub Offset"); + tcase_add_checked_fixture(tc_offset, setup, teardown); + tcase_add_test(tc_offset, PublisherOffsets); + tcase_add_test(tc_offset, SubscriberOffsets); + + Suite *s = suite_create("PubSub Offsets"); + suite_add_tcase(s, tc_offset); + + SRunner *sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr,CK_NORMAL); + int number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + From 01a372846cc7d3d8123bc42bc6de5b0cbe2c5ff4 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 18:15:28 +0100 Subject: [PATCH 135/158] refactor(example): Better naming of PubSub-RT examples --- examples/CMakeLists.txt | 10 ++++++---- ...h_rt.c => server_pubsub_publish_rt_state_machine.c} | 0 ...rt.c => server_pubsub_subscribe_rt_state_machine.c} | 0 3 files changed, 6 insertions(+), 4 deletions(-) rename examples/pubsub_realtime/{server_pubsub_publish_rt.c => server_pubsub_publish_rt_state_machine.c} (100%) rename examples/pubsub_realtime/{server_pubsub_subscribe_rt.c => server_pubsub_subscribe_rt_state_machine.c} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ad0af2f71..d497d6a5e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -187,10 +187,12 @@ if(UA_ENABLE_PUBSUB) add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) if(UA_ARCHITECTURE_POSIX) - add_example(server_pubsub_publish_rt pubsub_realtime/server_pubsub_publish_rt.c) - target_link_libraries(server_pubsub_publish_rt "rt") - add_example(server_pubsub_subscribe_rt pubsub_realtime/server_pubsub_subscribe_rt.c) - target_link_libraries(server_pubsub_subscribe_rt "rt") + add_example(server_pubsub_publish_rt_state_machine + pubsub_realtime/server_pubsub_publish_rt_state_machine.c) + target_link_libraries(server_pubsub_publish_rt_state_machine "rt") + add_example(server_pubsub_subscribe_rt_state_machine + pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c) + target_link_libraries(server_pubsub_subscribe_rt_state_machine "rt") endif() if(UA_ENABLE_ENCRYPTION_MBEDTLS) diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt.c b/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_publish_rt.c rename to examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c similarity index 100% rename from examples/pubsub_realtime/server_pubsub_subscribe_rt.c rename to examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c From a4310b14604bd79d49a1b5a88ce1f390e2993e24 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 18:19:45 +0100 Subject: [PATCH 136/158] feat(examples): Add examples for the RT offset table usage --- examples/CMakeLists.txt | 3 + examples/pubsub_realtime/README.md | 34 +++ .../server_pubsub_publish_rt_offsets.c | 154 ++++++++++ .../server_pubsub_subscribe_rt_offsets.c | 270 ++++++++++++++++++ 4 files changed, 461 insertions(+) create mode 100644 examples/pubsub_realtime/README.md create mode 100644 examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c create mode 100644 examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d497d6a5e..4163c35f3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -186,6 +186,9 @@ if(UA_ENABLE_PUBSUB) add_example(server_pubsub_publisher_iop pubsub/server_pubsub_publisher_iop.c) add_example(pubsub_subscribe_standalone_dataset pubsub/pubsub_subscribe_standalone_dataset.c) + add_example(server_pubsub_publish_rt_offsets pubsub_realtime/server_pubsub_publish_rt_offsets.c) + add_example(server_pubsub_subscribe_rt_offsets pubsub_realtime/server_pubsub_subscribe_rt_offsets.c) + if(UA_ARCHITECTURE_POSIX) add_example(server_pubsub_publish_rt_state_machine pubsub_realtime/server_pubsub_publish_rt_state_machine.c) diff --git a/examples/pubsub_realtime/README.md b/examples/pubsub_realtime/README.md new file mode 100644 index 000000000..7b1a2a826 --- /dev/null +++ b/examples/pubsub_realtime/README.md @@ -0,0 +1,34 @@ +# open62541 Realtime-PubSub Support + +Two features of open625451 enable realtime Pubsub: The custom state machine for +PubSub-Components and the generation of offset tables for the PubSub +NetworkMessage. Both features are showcased in the respective examples for +Publishers and Subscribers. + +## Custom State Machine + +The custom state machine mechanism lets user-defined code control the state of +the PubSub-Components (PubSubConnection, ReaderGroup, Reader, ...). Hence the +user-defined code is also responsible to register time-based events, network +connections, and so on. + +This allows the integration with non-POSIX networking APIs for publishers and +subscribers. Note that the provided examples are still POSIX based, but handle +all sockets in userland. + +## NetworkMessage Offset Table + +Typically realtime PubSub is used together with the periodic-fixed header layout +where the position of the content in the NetworkMessage does not change between +publish cycles. Then the NetworkMessage offset tables can be generated to speed +up the updating and parsing of NetworkMessages. For the relevant fields, the +well-known offsets are computed ahead of time. Each offset furthermore has a +known source (the NodeId of the respective PubSub-Component in the information +model). + +The NetworkMessage offset table is usually generated when the +ReaderGroup/WriterGroup changes its state to OPERATIONAL. At this time, from the +offset table, each field of the NetworkMessage is resolved to point to a backend +information source with fast access. Then the updating/parsing of the +NetworkMessage in the publish interval is simply a matter of looping over the +offset table and performing mostly memcpy steps. diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c new file mode 100644 index 000000000..9e011811d --- /dev/null +++ b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c @@ -0,0 +1,154 @@ +/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. + * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ + +#include +#include + +#include + +#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100 +#define PUBSUB_CONFIG_FIELD_COUNT 10 + +static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; + +/* Values in static locations. We cycle the dvPointers double-pointer to the + * next with atomic operations. */ +UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT]; +UA_NodeId publishVariables[PUBSUB_CONFIG_FIELD_COUNT]; + +int main(void) { + /* Prepare the values */ + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + valueStore[i] = (UA_UInt32) i + 1; + UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]); + dvStore[i].hasValue = true; + dvPointers[i] = &dvStore[i]; + } + + /* Initialize the server */ + UA_Server *server = UA_Server_new(); + + /* Add a PubSubConnection */ + UA_PubSubConnectionConfig connectionConfig; + memset(&connectionConfig, 0, sizeof(connectionConfig)); + connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); + connectionConfig.transportProfileUri = + UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + UA_NetworkAddressUrlDataType networkAddressUrl = + {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + connectionConfig.publisherId.id.uint16 = 2234; + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); + + /* Add a PublishedDataSet */ + UA_PublishedDataSetConfig publishedDataSetConfig; + memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); + publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; + publishedDataSetConfig.name = UA_STRING("Demo PDS"); + UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); + + /* Add DataSetFields with static value source to PDS */ + UA_DataSetFieldConfig dsfConfig; + for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + /* Create the variable */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), + UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(HASCOMPONENT), + UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NS0ID(BASEDATAVARIABLETYPE), + vAttr, NULL, &publishVariables[i]); + + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + memset(&valueBackend, 0, sizeof(UA_ValueBackend)); + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &dvPointers[i]; + UA_Server_setVariableNode_valueBackend(server, publishVariables[i], valueBackend); + + /* Add the DataSetField */ + memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); + dsfConfig.field.variable.publishParameters.publishedVariable = publishVariables[i]; + dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; + dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; + UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); + } + + /* Add a WriterGroup */ + UA_WriterGroupConfig writerGroupConfig; + memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); + writerGroupConfig.name = UA_STRING("Demo WriterGroup"); + writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; + writerGroupConfig.writerGroupId = 100; + writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; + writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; + + /* Change message settings of writerGroup to send PublisherId, WriterGroupId + * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ + UA_UadpWriterGroupMessageDataType writerGroupMessage; + UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); + writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_GROUPVERSION | + UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, + &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); + + UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent); + + /* Add a DataSetWriter to the WriterGroup */ + UA_NodeId dataSetWriterIdent; + UA_DataSetWriterConfig dataSetWriterConfig; + memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); + dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); + dataSetWriterConfig.dataSetWriterId = 62541; + dataSetWriterConfig.keyFrameCount = 10; + dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType; + UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType); + uadpDataSetWriterMessageDataType.dataSetMessageContentMask = + UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings, + &uadpDataSetWriterMessageDataType, + &UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]); + + UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, + &dataSetWriterConfig, &dataSetWriterIdent); + + /* Print the Offset Table */ + UA_PubSubOffsetTable ot; + UA_Server_computeWriterGroupOffsetTable(server, writerGroupIdent, &ot); + for(size_t i = 0; i < ot.offsetsSize; i++) { + UA_String out = UA_STRING_NULL; + if(ot.offsets[i].offsetType >= UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE) { + /* For writers the component is the NodeId is the DataSetField. + * Instead print the source node that contains the data */ + UA_DataSetFieldConfig dsfc; + UA_Server_getDataSetFieldConfig(server, ot.offsets[i].component, &dsfc); + UA_NodeId_print(&dsfc.field.variable.publishParameters.publishedVariable, &out); + UA_DataSetFieldConfig_clear(&dsfc); + } else { + UA_NodeId_print(&ot.offsets[i].component, &out); + } + printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n", + (unsigned)i, (unsigned)ot.offsets[i].offset, + (unsigned)ot.offsets[i].offsetType, + (int)out.length, out.data); + UA_String_clear(&out); + } + + /* Cleanup */ + UA_PubSubOffsetTable_clear(&ot); + UA_Server_delete(server); + return EXIT_SUCCESS; +} + diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c new file mode 100644 index 000000000..ed087eae3 --- /dev/null +++ b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c @@ -0,0 +1,270 @@ +/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. + * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ + +#include +#include +#include + +#include + +#define PUBSUB_CONFIG_FIELD_COUNT 10 + +static UA_NetworkAddressUrlDataType networkAddressUrl = + {{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")}; +static UA_String transportProfile = + UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); + +/** + * The main target of this example is to reduce the time spread and effort + * during the subscribe cycle. This RT level example is based on buffered + * DataSetMessages and NetworkMessages. Since changes in the + * PubSub-configuration will invalidate the buffered frames, the PubSub + * configuration must be frozen after the configuration phase. + * + * After enabling the subscriber (and when the first message is received), the + * NetworkMessages and DataSetMessages will be calculated and buffered. During + * the subscribe cycle, decoding will happen only to the necessary offsets and + * the buffered NetworkMessage will only be updated. + */ + +UA_NodeId connectionIdentifier; +UA_NodeId readerGroupIdentifier; +UA_NodeId readerIdentifier; + +UA_Server *server; + +UA_DataSetReaderConfig readerConfig; + +/* Simulate a custom data sink (e.g. shared memory) */ +UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; +UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; + +/* Define MetaData for TargetVariables */ +static void +fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { + if(pMetaData == NULL) + return; + + UA_DataSetMetaDataType_init (pMetaData); + pMetaData->name = UA_STRING ("DataSet 1"); + + /* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT + * to create targetVariables */ + pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT; + pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, + &UA_TYPES[UA_TYPES_FIELDMETADATA]); + + for(size_t i = 0; i < pMetaData->fieldsSize; i++) { + /* UInt32 DataType */ + UA_FieldMetaData_init (&pMetaData->fields[i]); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, + &pMetaData->fields[i].dataType); + pMetaData->fields[i].builtInType = UA_NS0ID_UINT32; + pMetaData->fields[i].name = UA_STRING ("UInt32 varibale"); + pMetaData->fields[i].valueRank = -1; /* scalar */ + } +} + +/* Add new connection to the server */ +static void +addPubSubConnection(UA_Server *server) { + /* Configuration creation for the connection */ + UA_PubSubConnectionConfig connectionConfig; + memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); + connectionConfig.name = UA_STRING("UDPMC Connection 1"); + connectionConfig.transportProfileUri = transportProfile; + UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, + &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); + connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; + connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); + UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); +} + +/* Add ReaderGroup to the created connection */ +static void +addReaderGroup(UA_Server *server) { + UA_ReaderGroupConfig readerGroupConfig; + memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); + readerGroupConfig.name = UA_STRING("ReaderGroup1"); + readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; + UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, + &readerGroupIdentifier); +} + +/* Set SubscribedDataSet type to TargetVariables data type + * Add subscribedvariables to the DataSetReader */ +static void +addSubscribedVariables (UA_Server *server) { + UA_NodeId folderId; + UA_NodeId newnodeId; + UA_String folderName = readerConfig.dataSetMetaData.name; + UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; + UA_QualifiedName folderBrowseName; + if(folderName.length > 0) { + oAttr.displayName.locale = UA_STRING ("en-US"); + oAttr.displayName.text = folderName; + folderBrowseName.namespaceIndex = 1; + folderBrowseName.name = folderName; + } else { + oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); + folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); + } + + UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER), + UA_NS0ID(ORGANIZES), folderBrowseName, + UA_NS0ID(BASEOBJECTTYPE), oAttr, + NULL, &folderId); + + /* Set the subscribed data to TargetVariable type */ + readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; + /* Create the TargetVariables with respect to DataSetMetaData fields */ + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = + readerConfig.dataSetMetaData.fieldsSize; + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = + (UA_FieldTargetVariable *)UA_calloc( + readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, + sizeof(UA_FieldTargetVariable)); + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + /* Variable to subscribe data */ + UA_VariableAttributes vAttr = UA_VariableAttributes_default; + vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); + vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; + // Initialize the values at first to create the buffered NetworkMessage + // with correct size and offsets + UA_Variant value; + UA_Variant_init(&value); + UA_UInt32 intValue = 0; + UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]); + vAttr.value = value; + UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), + folderId, UA_NS0ID(HASCOMPONENT), + UA_QUALIFIEDNAME(1, "Subscribed UInt32"), + UA_NS0ID(BASEDATAVARIABLETYPE), + vAttr, NULL, &newnodeId); + repeatedFieldValues[i] = 0; + repeatedDataValueRT[i] = UA_DataValue_new(); + UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], + &UA_TYPES[UA_TYPES_UINT32]); + repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; + repeatedDataValueRT[i]->hasValue = true; + + /* Set the value backend of the above create node to 'external value source' */ + UA_ValueBackend valueBackend; + memset(&valueBackend, 0, sizeof(UA_ValueBackend)); + valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; + valueBackend.backend.external.value = &repeatedDataValueRT[i]; + UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); + + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + + /* For creating Targetvariables */ + UA_FieldTargetDataType_init(ftdt); + ftdt->attributeId = UA_ATTRIBUTEID_VALUE; + ftdt->targetNodeId = newnodeId; + } +} + +/* Add DataSetReader to the ReaderGroup */ +static void +addDataSetReader(UA_Server *server) { + memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); + readerConfig.name = UA_STRING("DataSet Reader 1"); + /* Parameters to filter which DataSetMessage has to be processed + * by the DataSetReader */ + /* The following parameters are used to show that the data published by + * tutorial_pubsub_publish.c is being subscribed and is being updated in + * the information model */ + UA_UInt16 publisherIdentifier = 2234; + readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; + readerConfig.publisherId.id.uint16 = publisherIdentifier; + readerConfig.writerGroupId = 100; + readerConfig.dataSetWriterId = 62541; + readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; + readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW; + readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask) + (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | + UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | + UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); + dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER; + readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; + + readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; + + /* Setting up Meta data configuration in DataSetReader */ + fillTestDataSetMetaData(&readerConfig.dataSetMetaData); + + addSubscribedVariables(server); + UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); + + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { + UA_FieldTargetVariable *tv = + &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_FieldTargetDataType *ftdt = &tv->targetVariable; + UA_FieldTargetDataType_clear(ftdt); + } + + UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.dataSetMetaData.fields); + UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); +} + +int main(int argc, char **argv) { + if(argc > 1) { + if(strcmp(argv[1], "-h") == 0) { + printf("usage: %s [device]\n", argv[0]); + return EXIT_SUCCESS; + } else if(strncmp(argv[1], "opc.udp://", 10) == 0) { + networkAddressUrl.url = UA_STRING(argv[1]); + } else if(strncmp(argv[1], "opc.eth://", 10) == 0) { + transportProfile = + UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); + if(argc < 3) { + printf("Error: UADP/ETH needs an interface name\n"); + return EXIT_FAILURE; + } + networkAddressUrl.networkInterface = UA_STRING(argv[2]); + networkAddressUrl.url = UA_STRING(argv[1]); + } else { + printf ("Error: unknown URI\n"); + return EXIT_FAILURE; + } + } + if(argc > 2) + networkAddressUrl.networkInterface = UA_STRING(argv[2]); + + /* Return value initialized to Status Good */ + UA_StatusCode retval = UA_STATUSCODE_GOOD; + server = UA_Server_new(); + + addPubSubConnection(server); + addReaderGroup(server); + addDataSetReader(server); + + /* Print the Offset Table */ + UA_PubSubOffsetTable ot; + UA_Server_computeReaderGroupOffsetTable(server, readerGroupIdentifier, &ot); + for(size_t i = 0; i < ot.offsetsSize; i++) { + UA_String out = UA_STRING_NULL; + UA_NodeId_print(&ot.offsets[i].component, &out); + printf("%u:\tOffset %u\tOffsetType %u\tComponent %.*s\n", + (unsigned)i, (unsigned)ot.offsets[i].offset, + (unsigned)ot.offsets[i].offsetType, + (int)out.length, out.data); + UA_String_clear(&out); + } + + UA_Server_delete(server); + + UA_PubSubOffsetTable_clear(&ot); + for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { + UA_DataValue_delete(repeatedDataValueRT[i]); + } + + return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; +} + From 4e2bc909bbec632766f39123eda7b540bd892b77 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 20:26:56 +0100 Subject: [PATCH 137/158] fix(core): Remove /examples and /docs from the .gitignore Also remove some files from the list that no longer exists since a long time. --- .gitignore | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.gitignore b/.gitignore index 884759185..47aca1d02 100644 --- a/.gitignore +++ b/.gitignore @@ -58,27 +58,13 @@ coverage_report /.settings .autotools test-driver -include/opcua.h -include/ua_namespace_0.h -src/opcua.c -src/ua_namespace_0.c -tests/check_builtin -tests/check_create -tests/check_decode -tests/check_delete -tests/check_encode -tests/check_memory -tests/check_namespace -tests/coverage tests/**/CMakeFiles Testing Makefile /CMakeCache.txt /CMakeFiles/ /cmake_install.cmake -doc/ doc_src/ -examples/ /exampleClient /exampleClient_legacy /src_generated/ From 853f1486593f02de41d71873eb0b248fab5fa291 Mon Sep 17 00:00:00 2001 From: Florian La Roche Date: Wed, 15 Jan 2025 17:04:26 +0100 Subject: [PATCH 138/158] refactor(tools): Use pylint suggestions to refactor python code (#7016) Use pylint suggestions to refactor python code. Signed-off-by: Florian La Roche Co-authored-by: Julius Pfrommer --- tools/amalgamate.py | 4 +-- tools/c2rst.py | 2 +- tools/gdb-prettyprint.py | 24 +++++++------- tools/generate_datatypes.py | 2 +- tools/generate_nodeid_header.py | 2 +- tools/generate_statuscode_descriptions.py | 2 +- .../backend_open62541_typedefinitions.py | 2 +- tools/nodeset_compiler/datatypes.py | 12 +++---- tools/nodeset_compiler/nodes.py | 26 +++++++-------- tools/nodeset_compiler/nodeset.py | 4 +-- tools/nodeset_compiler/nodeset_testing.py | 2 +- tools/nodeset_compiler/type_parser.py | 32 +++++++------------ .../generate_nodesetinjector_content.py | 12 +++---- 13 files changed, 58 insertions(+), 68 deletions(-) diff --git a/tools/amalgamate.py b/tools/amalgamate.py index ec95ca295..edc7a1387 100755 --- a/tools/amalgamate.py +++ b/tools/amalgamate.py @@ -79,7 +79,7 @@ for fname in args.inputs: for fname in args.inputs: with open(fname, encoding='utf8', errors='replace') as infile: file.write("\n/**** amalgamated original file \"" + fname[initial:] + "\" ****/\n\n") - print ("Integrating file '" + fname + "' ... ", end=""), + print ("Integrating file '" + fname + "' ... ", end="") for line in infile: inc_res = include_re.match(line) guard_res = guard_re.match(line) @@ -88,7 +88,7 @@ for fname in args.inputs: # Ensure file is written to disk. file.flush() os.fsync(file.fileno()) - print ("done."), + print ("done.") if not is_c: file.write("#endif /* %s */\n" % (outname.upper() + "_H_")) diff --git a/tools/c2rst.py b/tools/c2rst.py index 5b7b78e91..52466086c 100755 --- a/tools/c2rst.py +++ b/tools/c2rst.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this +# License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import sys diff --git a/tools/gdb-prettyprint.py b/tools/gdb-prettyprint.py index 59543a8f5..134c89607 100644 --- a/tools/gdb-prettyprint.py +++ b/tools/gdb-prettyprint.py @@ -125,7 +125,7 @@ class ExtensionObject: content = self.val['content'] if encoding == 0: return "UA_ExtensionObject()" - elif encoding == 1 or encoding == 2: + if encoding in (1, 2): encoded = content['encoded'] return "UA_ExtensionObject({}, {})".format(encoded['typeId'], encoded['body']) decoded = content['decoded'] @@ -165,17 +165,17 @@ class Variant: return "UA_Variant<%s[%i]>(%s, arrayDimensions = %s)" % (tt, array_length, content, dims) def build_open62541_printer(): - pp = gdb.printing.RegexpCollectionPrettyPrinter("open62541") - pp.add_printer('UA_String', '^UA_String$', String) - pp.add_printer('UA_ByteString', '^UA_ByteString$', ByteString) - pp.add_printer('UA_LocalizedText', '^UA_LocalizedText$', LocalizedText) - pp.add_printer('UA_QualifiedName', '^UA_QualifiedName$', QualifiedName) - pp.add_printer('UA_Guid', '^UA_Guid$', Guid) - pp.add_printer('UA_NodeId', '^UA_NodeId$', NodeId) - pp.add_printer('UA_ExpandedNodeId', '^UA_ExpandedNodeId$', ExpandedNodeId) - pp.add_printer('UA_ExtensionObject', '^UA_ExtensionObject$', ExtensionObject) - pp.add_printer('UA_Variant', '^UA_Variant$', Variant) - return pp + pp = gdb.printing.RegexpCollectionPrettyPrinter("open62541") + pp.add_printer('UA_String', '^UA_String$', String) + pp.add_printer('UA_ByteString', '^UA_ByteString$', ByteString) + pp.add_printer('UA_LocalizedText', '^UA_LocalizedText$', LocalizedText) + pp.add_printer('UA_QualifiedName', '^UA_QualifiedName$', QualifiedName) + pp.add_printer('UA_Guid', '^UA_Guid$', Guid) + pp.add_printer('UA_NodeId', '^UA_NodeId$', NodeId) + pp.add_printer('UA_ExpandedNodeId', '^UA_ExpandedNodeId$', ExpandedNodeId) + pp.add_printer('UA_ExtensionObject', '^UA_ExtensionObject$', ExtensionObject) + pp.add_printer('UA_Variant', '^UA_Variant$', Variant) + return pp gdb.printing.register_pretty_printer(gdb.current_objfile(), build_open62541_printer()) diff --git a/tools/generate_datatypes.py b/tools/generate_datatypes.py index 85c0d8b76..cf39f5c3d 100755 --- a/tools/generate_datatypes.py +++ b/tools/generate_datatypes.py @@ -4,9 +4,9 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +import argparse from nodeset_compiler.type_parser import CSVBSDTypeParser import nodeset_compiler.backend_open62541_typedefinitions as backend -import argparse ############################### # Parse the Command Line Input# diff --git a/tools/generate_nodeid_header.py b/tools/generate_nodeid_header.py index 6ad9b12f8..51b4dc7f1 100755 --- a/tools/generate_nodeid_header.py +++ b/tools/generate_nodeid_header.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this +# License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import argparse diff --git a/tools/generate_statuscode_descriptions.py b/tools/generate_statuscode_descriptions.py index 60e527e4c..dd0894761 100755 --- a/tools/generate_statuscode_descriptions.py +++ b/tools/generate_statuscode_descriptions.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this +# License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import argparse diff --git a/tools/nodeset_compiler/backend_open62541_typedefinitions.py b/tools/nodeset_compiler/backend_open62541_typedefinitions.py index 5ab98cbaf..13d4f6ad4 100644 --- a/tools/nodeset_compiler/backend_open62541_typedefinitions.py +++ b/tools/nodeset_compiler/backend_open62541_typedefinitions.py @@ -380,7 +380,7 @@ class CGenerator: typeFile = arr.lower() typeFile = typeFile[typeFile.startswith("ua_") and len("ua_"):] additionalHeaders += """#include "%s_generated.h"\n""" % typeFile - + self.printh('''/********************************** * Autogenerated -- do not modify * **********************************/ diff --git a/tools/nodeset_compiler/datatypes.py b/tools/nodeset_compiler/datatypes.py index e000475d0..f3f492758 100644 --- a/tools/nodeset_compiler/datatypes.py +++ b/tools/nodeset_compiler/datatypes.py @@ -235,12 +235,12 @@ class Value: if ebodypart.localName == "EncodingMask": ebodypart = getNextElementNode(ebodypart) # No optional fields are set. - if(ebodypart is None): + if ebodypart is None: members = [] # The SwitchField must be checked. ebodypart could be None if only optional fields are included # in the ExtensionObject and none of them is set. - if(ebodypart is not None): + if ebodypart is not None: if ebodypart.localName == "SwitchField": # The switch field is the index of the available union fields starting with 1 data = int(ebodypart.firstChild.data) @@ -306,8 +306,8 @@ class Value: logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a BuildinType, StructType or EnumerationType.") return extobj else: - logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a StructMember.") - return extobj + logger.error(str(parent.id) + ": Description of dataType " + str(parentDataTypeNode.browseName) + " in ExtensionObject is not a StructMember.") + return extobj ebodypart = getNextElementNode(ebodypart) @@ -338,7 +338,7 @@ class Value: else: logger.error(str(parent.id) + ": Could not parse for Union.") return self - + childValue = ebodypart.firstChild if not childValue.nodeType == ebodypart.ELEMENT_NODE: @@ -669,7 +669,7 @@ class NodeId(Value): for p in idparts: if p[:2] == "ns": self.ns = int(p[3:]) - if(len(namespaceMapping.values()) > 0): + if len(namespaceMapping.values()) > 0: self.ns = namespaceMapping[self.ns] elif p[:2] == "i=": self.i = int(p[2:]) diff --git a/tools/nodeset_compiler/nodes.py b/tools/nodeset_compiler/nodes.py index e52f601c8..5e056b145 100644 --- a/tools/nodeset_compiler/nodes.py +++ b/tools/nodeset_compiler/nodes.py @@ -263,7 +263,7 @@ class VariableNode(Node): elif x.localName == "ArrayDimensions" and len(self.arrayDimensions) == 0: elements = x.getElementsByTagName("ListOfUInt32") if len(elements): - for idx, v in enumerate(elements[0].getElementsByTagName("UInt32")): + for _, v in enumerate(elements[0].getElementsByTagName("UInt32")): self.arrayDimensions.append(v.firstChild.data) elif x.localName == "AccessLevel": self.accessLevel = int(x.firstChild.data) @@ -412,8 +412,7 @@ class DataTypeNode(Node): raise Exception("Encoding needs to be built first using buildEncoding()") if not self.__encodable__: return [] - else: - return self.__baseTypeEncoding__ + return self.__baseTypeEncoding__ def buildEncoding(self, nodeset, indent=0, force=False, namespaceMapping=None): @@ -455,7 +454,7 @@ class DataTypeNode(Node): prefix = " " + "|" * indent + "+" - if force==True: + if force is True: self.__encodable__ = None if self.__encodable__ is not None and self.__encodable__: @@ -530,7 +529,7 @@ class DataTypeNode(Node): enumVal = "" valueRank = None #symbolicName = None - arrayDimensions = None + #arrayDimensions = None isOptional = "" for at,av in x.attributes.items(): if at == "DataType": @@ -540,9 +539,8 @@ class DataTypeNode(Node): elif at == "Name": fname = str(av) elif at == "SymbolicName": - # ignore - continue - # symbolicName = str(av) + pass # ignore + #symbolicName = str(av) elif at == "Value": enumVal = int(av) isEnum = True @@ -551,10 +549,10 @@ class DataTypeNode(Node): elif at == "IsOptional": isOptional = str(av) elif at == "ArrayDimensions": - arrayDimensions = int(av) + pass # ignore + #arrayDimensions = int(av) elif at == "AllowSubTypes": - # ignore - continue + pass # ignore else: logger.warn("Unknown Field Attribute " + str(at)) # This can either be an enumeration OR a structure, not both. @@ -571,7 +569,7 @@ class DataTypeNode(Node): # This might be a subtype... follow the node defined as datatype to find out # what encoding to use fdTypeNodeId = NodeId(fdtype) - if namespaceMapping != None: + if namespaceMapping is not None: fdTypeNodeId.ns = namespaceMapping[fdTypeNodeId.ns] if fdTypeNodeId not in nodeset.nodes: raise Exception(f"Node {fdTypeNodeId} not found in nodeset") @@ -600,7 +598,7 @@ class DataTypeNode(Node): while len(self.__baseTypeEncoding__) == 1 and isinstance(self.__baseTypeEncoding__[0], list): self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0] - if isOptionSet == True: + if isOptionSet is True: self.__isOptionSet__ = True subenc = parentType.buildEncoding(nodeset=nodeset, namespaceMapping=namespaceMapping) if not parentType.isEncodable(): @@ -610,7 +608,7 @@ class DataTypeNode(Node): self.__definition__ = enumDict return self.__baseTypeEncoding__ - if isEnum == True: + if isEnum is True: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32'] self.__definition__ = enumDict self.__isEnum__ = True diff --git a/tools/nodeset_compiler/nodeset.py b/tools/nodeset_compiler/nodeset.py index 3d9b18306..737d5a0f8 100644 --- a/tools/nodeset_compiler/nodeset.py +++ b/tools/nodeset_compiler/nodeset.py @@ -37,7 +37,7 @@ def getSubTypesOf(nodeset, node, skipNodes=[]): re = set() re.add(node) for ref in node.references: - if (ref.referenceType == hassubtype): + if ref.referenceType == hassubtype: skipAll = set() skipAll.update(skipNodes) skipAll.update(re) @@ -111,7 +111,7 @@ class NodeSet: def sanitize(self): for n in self.nodes.values(): - if n.sanitize() == False: + if n.sanitize() is False: raise Exception("Failed to sanitize node " + str(n)) # Sanitize reference consistency diff --git a/tools/nodeset_compiler/nodeset_testing.py b/tools/nodeset_compiler/nodeset_testing.py index 573750421..78cfb6370 100644 --- a/tools/nodeset_compiler/nodeset_testing.py +++ b/tools/nodeset_compiler/nodeset_testing.py @@ -46,7 +46,7 @@ class testing: for n in ns: tmp.append(n) for r in n.getReferences(): - if (r.target() not in tmp): + if r.target() not in tmp: tmp.append(r.target()) print("...tmp, " + str(len(tmp)) + " nodes discovered") ns = [] diff --git a/tools/nodeset_compiler/type_parser.py b/tools/nodeset_compiler/type_parser.py index c6e804dd3..ea07797ce 100644 --- a/tools/nodeset_compiler/type_parser.py +++ b/tools/nodeset_compiler/type_parser.py @@ -3,11 +3,10 @@ import codecs import csv import json import xml.etree.ElementTree as etree +import xml.dom.minidom as dom import copy import re from collections import OrderedDict -import sys -import xml.dom.minidom as dom try: from opaque_type_mapping import get_base_type_for_opaque as get_base_type_for_opaque_ns0 @@ -46,8 +45,7 @@ class TypeNotDefinedException(Exception): def get_base_type_for_opaque(name): if name in user_opaque_type_mapping: return user_opaque_type_mapping[name] - else: - return get_base_type_for_opaque_ns0(name) + return get_base_type_for_opaque_ns0(name) def get_type_name(xml_type_name): [namespace, type_name] = xml_type_name.split(':', 1) @@ -59,11 +57,9 @@ def get_type_for_name(xml_type_name, types, xmlNamespaces): if resultNs == 'http://opcfoundation.org/BinarySchema/': resultNs = 'http://opcfoundation.org/UA/' if resultNs not in types: - raise TypeNotDefinedException("Unknown namespace: '{resultNs}'".format( - resultNs=resultNs)) + raise TypeNotDefinedException(f"Unknown namespace: '{resultNs}'") if member_type_name not in types[resultNs]: - raise TypeNotDefinedException("Unknown type: '{type}'".format( - type=member_type_name)) + raise TypeNotDefinedException(f"Unknown type: '{member_type_name}'") return types[resultNs][member_type_name] @@ -98,7 +94,7 @@ class EnumerationType(Type): Type.__init__(self, outname, xml, namespace) self.pointerfree = True self.elements = OrderedDict() - self.isOptionSet = True if xml.get("IsOptionSet", "false") == "true" else False + self.isOptionSet = bool(xml.get("IsOptionSet", "false") == "true") self.lengthInBits = 0 try: self.lengthInBits = int(xml.get("LengthInBits", "32")) @@ -113,7 +109,7 @@ class EnumerationType(Type): self.strTypeIndex = "UA_TYPES_INT32" # special handling for OptionSet datatype (bitmask) - if self.isOptionSet == True: + if self.isOptionSet is True: if self.lengthInBits <= 8: self.strDataType = "UA_Byte" self.strTypeKind = "UA_DATATYPEKIND_BYTE" @@ -164,7 +160,7 @@ class StructType(Type): typename = type_aliases.get(xml.get("Name"), xml.get("Name")) bt = xml.get("BaseType") - self.is_union = True if bt and get_type_name(bt)[1] == "Union" else False + self.is_union = bool(bt and get_type_name(bt)[1] == "Union") for child in xml: length_field = child.get("LengthField") if length_field: @@ -187,13 +183,10 @@ class StructType(Type): if self.is_union and child.get("Name") in switch_fields: continue switch_field = child.get("SwitchField") - if switch_field and switch_field in optional_fields: - member_is_optional = True - else: - member_is_optional = False + member_is_optional = (switch_field and switch_field in optional_fields) member_name = child.get("Name") member_name = member_name[:1].lower() + member_name[1:] - is_array = True if child.get("LengthField") else False + is_array = bool(child.get("LengthField")) member_type_name = get_type_name(child.get("TypeName"))[1] if member_type_name == typename: # If a type contains itself, use self as member_type @@ -246,7 +239,7 @@ class TypeParser(): for child in element: if child.tag == "{http://opcfoundation.org/BinarySchema/}Field": childname = get_type_name(child.get("TypeName"))[1] - if childname != "Bit" and childname != parentname: + if childname not in ("Bit", parentname): try: get_type_for_name(child.get("TypeName"), types, xmlNamespaces) except TypeNotDefinedException: @@ -279,8 +272,7 @@ class TypeParser(): elif child.get("Name") == "Reserved1": if len(opt_fields) + int(child.get("Length")) != 32: return False - else: - break + break else: return False else: @@ -412,7 +404,7 @@ class CSVBSDTypeParser(TypeParser): for ns in self.types: for t in self.types[ns]: if isinstance(self.types[ns][t], BuiltinType): - del self.existing_types[ns][t] + del self.existing_types[ns][t] # parse the new types for f in self.type_bsd: diff --git a/tools/nodeset_injector/generate_nodesetinjector_content.py b/tools/nodeset_injector/generate_nodesetinjector_content.py index 2de512b95..b1b93ae9e 100644 --- a/tools/nodeset_injector/generate_nodesetinjector_content.py +++ b/tools/nodeset_injector/generate_nodesetinjector_content.py @@ -60,14 +60,14 @@ def unix_exec(): global data with open(args.outfile + ".c", "r+", encoding='utf8') as file: - # Acquire a lock on the file - fcntl.flock(file, fcntl.LOCK_EX) + # Acquire a lock on the file + fcntl.flock(file, fcntl.LOCK_EX) - # Write to the file - write_code_generation(file) + # Write to the file + write_code_generation(file) - # Release the lock on the file - fcntl.flock(file, fcntl.LOCK_UN) + # Release the lock on the file + fcntl.flock(file, fcntl.LOCK_UN) def print_include(string): From e37c40e2ea6ca1da41fbe846a87af59f4401041d Mon Sep 17 00:00:00 2001 From: Tomi Takala Date: Fri, 10 Jan 2025 14:50:14 +0200 Subject: [PATCH 139/158] fix(server): Use length to check private key validity --- src/server/ua_server_ns0_gds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/ua_server_ns0_gds.c b/src/server/ua_server_ns0_gds.c index f61f14e65..996f1bc75 100644 --- a/src/server/ua_server_ns0_gds.c +++ b/src/server/ua_server_ns0_gds.c @@ -297,7 +297,7 @@ updateCertificate(UA_Server *server, /* Verify that the privateKey is in a supported format and * that it matches the specified certificate */ - if(privateKey && privateKey->data) { + if(privateKey && privateKey->length > 0) { const UA_String pemFormat = UA_STRING("PEM"); const UA_String derFormat = UA_STRING("DER"); if(!UA_String_equal(&pemFormat, privateKeyFormat) && !UA_String_equal(&derFormat, privateKeyFormat)) From 5e053668fa7bcc21751e9c654c42d855fa74e857 Mon Sep 17 00:00:00 2001 From: Tomi Takala Date: Fri, 10 Jan 2025 14:51:48 +0200 Subject: [PATCH 140/158] fix(server): Remove declaration of 'i' hides previous local declaration warning --- src/server/ua_server_ns0_gds.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/ua_server_ns0_gds.c b/src/server/ua_server_ns0_gds.c index 996f1bc75..a21992cf8 100644 --- a/src/server/ua_server_ns0_gds.c +++ b/src/server/ua_server_ns0_gds.c @@ -1146,10 +1146,10 @@ applyChangesToServer(UA_Server *server) { UA_ByteString certificate = certInfo.certificate; UA_ByteString privateKey = certInfo.privateKey; - for(size_t i = 0; i < server->config.endpointsSize; i++) { - UA_EndpointDescription *ed = &server->config.endpoints[i]; + for(size_t j = 0; j < server->config.endpointsSize; j++) { + UA_EndpointDescription *ed = &server->config.endpoints[j]; UA_SecurityPolicy *sp = getSecurityPolicyByUri(server, - &server->config.endpoints[i].securityPolicyUri); + &server->config.endpoints[j].securityPolicyUri); UA_CHECK_MEM(sp, return UA_STATUSCODE_BADINTERNALERROR); if(!UA_NodeId_equal(&sp->certificateTypeId, &certTypeId)) From 05fa25911f869a250ab8a3d1bdec930039804bcf Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 22:30:47 +0100 Subject: [PATCH 141/158] refactor(pubsub): Immediately generate the public PubSub offset table --- src/pubsub/ua_pubsub_networkmessage.h | 7 +- src/pubsub/ua_pubsub_networkmessage_binary.c | 209 +++++++------------ src/pubsub/ua_pubsub_reader.c | 3 +- src/pubsub/ua_pubsub_readergroup.c | 114 ++-------- src/pubsub/ua_pubsub_writergroup.c | 122 +++-------- 5 files changed, 130 insertions(+), 325 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index f18b355a7..c22e23c56 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -82,8 +82,8 @@ UA_StatusCode UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffer *buffer); size_t -UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( - const UA_NetworkMessage *p, UA_NetworkMessageOffsetBuffer *offsetBuffer); +UA_NetworkMessage_calcSizeBinaryWithOffsetTable(const UA_NetworkMessage *p, + UA_PubSubOffsetTable *ot); /** * DataSetMessage @@ -104,8 +104,7 @@ UA_StatusCode UA_DataSetMessage_decodeBinary(Ctx *ctx, UA_DataSetMessage *dst, UA_UInt16 dsmSize); size_t -UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage *p, - UA_NetworkMessageOffsetBuffer *offsetBuffer, +UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage *p, UA_PubSubOffsetTable *ot, size_t currentOffset); void UA_DataSetMessage_clear(UA_DataSetMessage *p); diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index 90e33d7aa..869ca7c9e 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -918,26 +918,24 @@ cleanup: } static UA_Boolean -increaseOffsetArray(UA_NetworkMessageOffsetBuffer *offsetBuffer) { - UA_NetworkMessageOffset *tmpOffsets = (UA_NetworkMessageOffset *) - UA_realloc(offsetBuffer->offsets, - sizeof(UA_NetworkMessageOffset) * - (offsetBuffer->offsetsSize + (size_t)1)); +incrOffsetTable(UA_PubSubOffsetTable *ot) { + UA_PubSubOffset *tmpOffsets = (UA_PubSubOffset *) + UA_realloc(ot->offsets, sizeof(UA_PubSubOffset) * (ot->offsetsSize + 1)); UA_CHECK_MEM(tmpOffsets, return false); - memset(&tmpOffsets[offsetBuffer->offsetsSize], 0, sizeof(UA_NetworkMessageOffset)); - offsetBuffer->offsets = tmpOffsets; - offsetBuffer->offsetsSize++; + memset(&tmpOffsets[ot->offsetsSize], 0, sizeof(UA_PubSubOffset)); + ot->offsets = tmpOffsets; + ot->offsetsSize++; return true; } size_t UA_NetworkMessage_calcSizeBinary(const UA_NetworkMessage *p) { - return UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(p, NULL); + return UA_NetworkMessage_calcSizeBinaryWithOffsetTable(p, NULL); } size_t -UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( - const UA_NetworkMessage *p, UA_NetworkMessageOffsetBuffer *offsetBuffer) { +UA_NetworkMessage_calcSizeBinaryWithOffsetTable(const UA_NetworkMessage *p, + UA_PubSubOffsetTable *ot) { size_t size = 1; /* byte */ if(UA_NetworkMessage_ExtendedFlags1Enabled(p)) { size += 1; /* byte */ @@ -946,15 +944,6 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( } if(p->publisherIdEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) - return 0; - - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_PUBLISHERID; - } - switch(p->publisherId.idType) { case UA_PUBLISHERIDTYPE_BYTE: size += 1; /* byte */ @@ -981,26 +970,17 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->groupHeaderEnabled) { size += 1; /* byte */ - if(p->groupHeader.writerGroupIdEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) - return 0; - - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_WRITERGROUPID; - } + if(p->groupHeader.writerGroupIdEnabled) size += 2; /* UA_UInt16_calcSizeBinary(&p->groupHeader.writerGroupId) */ - } if(p->groupHeader.groupVersionEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; } size += 4; /* UA_UInt32_calcSizeBinary(&p->groupHeader.groupVersion) */ } @@ -1010,15 +990,13 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( } if(p->groupHeader.sequenceNumberEnabled){ - if(offsetBuffer){ - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot){ + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].content.sequenceNumber = - p->groupHeader.sequenceNumber; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = + UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; } size += 2; /* UA_UInt16_calcSizeBinary(&p->groupHeader.sequenceNumber) */ } @@ -1029,34 +1007,27 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->networkMessageType != UA_NETWORKMESSAGE_DATASET) return 0; /* not implemented */ size += 1; /* p->payloadHeader.dataSetPayloadHeader.count */ - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) - return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_DATASETWRITERID; - } size += (size_t)(2LU * p->payload.dataSetPayload.dataSetMessagesSize); /* uint16 */ } if(p->timestampEnabled) { - if(offsetBuffer){ - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; } size += 8; /* UA_DateTime_calcSizeBinary(&p->timestamp) */ } if(p->picosecondsEnabled){ - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; } size += 2; /* UA_UInt16_calcSizeBinary(&p->picoseconds) */ } @@ -1084,18 +1055,10 @@ UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer( if(p->payloadHeaderEnabled && count > 1) size += (size_t)(2LU * count); /* DataSetMessagesSize (uint16) */ for(size_t i = 0; i < count; i++) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) - return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE; - } - /* size = ... as the original size is used as the starting point in * UA_DataSetMessage_calcSizeBinary */ UA_DataSetMessage *dsm = &p->payload.dataSetPayload.dataSetMessages[i]; - size = UA_DataSetMessage_calcSizeBinary(dsm, offsetBuffer, size); + size = UA_DataSetMessage_calcSizeBinary(dsm, ot, size); } if(p->securityEnabled && p->securityHeader.securityFooterEnabled) @@ -1624,21 +1587,14 @@ UA_DataSetMessage_decodeBinary(Ctx *ctx, UA_DataSetMessage *dst, UA_UInt16 dsmSi } size_t -UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, - UA_NetworkMessageOffsetBuffer *offsetBuffer, - size_t currentOffset) { - size_t size = currentOffset; - - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) +UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage *p, UA_PubSubOffsetTable *ot, + size_t size) { + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - UA_DataValue_init(&offsetBuffer->offsets[pos].content.value); - UA_Variant_setScalar(&offsetBuffer->offsets[pos].content.value.value, - &p->header.fieldEncoding, &UA_TYPES[UA_TYPES_UINT32]); - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_FIELDENCDODING; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE; } size += 1; /* byte: DataSetMessage Type + Flags */ @@ -1646,51 +1602,45 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, size += 1; /* byte */ if(p->header.dataSetMessageSequenceNrEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].content.sequenceNumber = - p->header.dataSetMessageSequenceNr; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; } size += 2; /* UA_UInt16_calcSizeBinary(&p->header.dataSetMessageSequenceNr) */ } if(p->header.timestampEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP; } size += 8; /* UA_DateTime_calcSizeBinary(&p->header.timestamp) */ } if(p->header.picoSecondsIncluded) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS; } size += 2; /* UA_UInt16_calcSizeBinary(&p->header.picoSeconds) */ } if(p->header.statusEnabled) { - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - offsetBuffer->offsets[pos].offset = size; - offsetBuffer->offsets[pos].contentType = - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS; + ot->offsets[pos].offset = size; + ot->offsets[pos].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS; } size += 2; /* UA_UInt16_calcSizeBinary(&p->header.status) */ } @@ -1712,37 +1662,35 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, size += 2; /* p->data.keyFrameData.fieldCount */ for(UA_UInt16 i = 0; i < p->data.keyFrameData.fieldCount; i++){ - UA_NetworkMessageOffset *nmo = NULL; + UA_PubSubOffset *offset = NULL; const UA_DataValue *v = &p->data.keyFrameData.dataSetFields[i]; - if(offsetBuffer) { - size_t pos = offsetBuffer->offsetsSize; - if(!increaseOffsetArray(offsetBuffer)) + if(ot) { + size_t pos = ot->offsetsSize; + if(!incrOffsetTable(ot)) return 0; - nmo = &offsetBuffer->offsets[pos]; - nmo->offset = size; + offset = &ot->offsets[pos]; + offset->offset = size; } if(p->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { - if(offsetBuffer) - nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT; + if(ot) + offset->offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT; size += UA_calcSizeBinary(&v->value, &UA_TYPES[UA_TYPES_VARIANT], NULL); } else if(p->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) { if(p->data.keyFrameData.dataSetFields != NULL) { - if(offsetBuffer) { + if(ot) { if(!v->value.type || !v->value.type->pointerFree) return 0; /* only integer types for now */ /* Count the memory size of the specific field */ - offsetBuffer->rawMessageLength += v->value.type->memSize; - nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW; + offset->offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; } UA_FieldMetaData *fmd = &p->data.keyFrameData.dataSetMetaDataType->fields[i]; /* For arrays add encoded array length (4 bytes for each dimension) */ size += fmd->arrayDimensionsSize * sizeof(UA_UInt32); - if(offsetBuffer) { - nmo->offset += fmd->arrayDimensionsSize * sizeof(UA_UInt32); - } + if(ot) + offset->offset += fmd->arrayDimensionsSize * sizeof(UA_UInt32); /* We need to know how many elements there are */ size_t elemCnt = 1; @@ -1765,22 +1713,20 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, } } else { /* get length calculated in UA_DataSetMessage_decodeBinary */ - if(offsetBuffer) { - offsetBuffer->rawMessageLength = p->data.keyFrameData.rawFields.length; - nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW; - } + if(ot) + offset->offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; size += p->data.keyFrameData.rawFields.length; /* no iteration needed */ break; } } else if(p->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) { - if(offsetBuffer) - nmo->contentType = UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE; + if(ot) + offset->offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE; size += UA_calcSizeBinary(v, &UA_TYPES[UA_TYPES_DATAVALUE], NULL); } } } else if(p->header.dataSetMessageType == UA_DATASETMESSAGE_DATADELTAFRAME) { - if(offsetBuffer) + if(ot) return 0; /* Not supported for RT */ if(p->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) @@ -1804,7 +1750,6 @@ UA_DataSetMessage_calcSizeBinary(UA_DataSetMessage* p, /* If the message is larger than the configuredSize, it shall be set to not valid */ if(p->configuredSize < size) p->header.dataSetMessageValid = UA_FALSE; - size = p->configuredSize; } diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index 34ee9fa27..cc5718a54 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -770,8 +770,7 @@ UA_DataSetReader_prepareOffsetBuffer(Ctx *ctx, UA_DataSetReader *reader, } /* Compute and store the offsets necessary to decode */ - size_t nmSize = - UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(nm, &reader->bufferedMessage); + size_t nmSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(nm, NULL); if(nmSize == 0) { UA_NetworkMessage_clear(nm); UA_free(nm); diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index e2b590508..98f8220c5 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -1406,14 +1406,10 @@ UA_Server_computeReaderGroupOffsetTable(UA_Server *server, memset(ot, 0, sizeof(UA_PubSubOffsetTable)); /* Define variables here to allow the goto cleanup later on */ - size_t newo = 0; size_t msgSize; size_t fieldindex = 0; UA_FieldTargetDataType *tv = NULL; - UA_NetworkMessageOffsetBuffer oldOffsetTable; - memset(&oldOffsetTable, 0, sizeof(UA_NetworkMessageOffsetBuffer)); - UA_NetworkMessage networkMessage; memset(&networkMessage, 0, sizeof(UA_NetworkMessage)); @@ -1441,8 +1437,7 @@ UA_Server_computeReaderGroupOffsetTable(UA_Server *server, /* Compute the message length and generate the old format offset-table (done * inside calcSizeBinary) */ - msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(&networkMessage, - &oldOffsetTable); + msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(&networkMessage, ot); if(msgSize == 0) { res = UA_STATUSCODE_BADINTERNALERROR; goto cleanup; @@ -1453,115 +1448,52 @@ UA_Server_computeReaderGroupOffsetTable(UA_Server *server, if(res != UA_STATUSCODE_GOOD) goto cleanup; - /* Allocate memory for the output offset table */ - ot->offsets = (UA_PubSubOffset*) - UA_calloc(oldOffsetTable.offsetsSize, sizeof(UA_PubSubOffset)); - if(!ot->offsets) { - res = UA_STATUSCODE_BADOUTOFMEMORY; - goto cleanup; - } - - /* Walk over the (old) offset table and convert to the new schema. - * In parallel, pick up the component NodeIds. */ + /* Pick up the component NodeIds */ dsr = NULL; - for(size_t oldo = 0; oldo < oldOffsetTable.offsetsSize; oldo++) { - switch(oldOffsetTable.offsets[oldo].contentType) { - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); - newo++; + for(size_t i = 0; i < ot->offsetsSize; i++) { + UA_PubSubOffset *o = &ot->offsets[i]; + switch(o->offsetType) { + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: + UA_NodeId_copy(&rg->head.identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&rg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE: dsr = (dsr == NULL) ? LIST_FIRST(&rg->readers) : LIST_NEXT(dsr, listEntry); fieldindex = 0; - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); - newo++; + /* fall through */ + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS: + UA_NodeId_copy(&dsr->head.identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsr->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE: tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. targetVariables[fieldindex].targetVariable; - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; - newo++; break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT: tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. targetVariables[fieldindex].targetVariable; - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; - newo++; break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW: tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. targetVariables[fieldindex].targetVariable; - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&tv->targetNodeId, &ot->offsets[newo].component); + UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; - newo++; break; default: break; } } - ot->offsetsSize = newo; - cleanup: /* Clean up and return */ - UA_NetworkMessageOffsetBuffer_clear(&oldOffsetTable); for(size_t i = 0; i < dsmCount; i++) { UA_DataSetMessage_clear(&dsmStore[i]); } diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index aeacd9543..5e6372444 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -352,8 +352,7 @@ UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { /* Compute the message length and generate the offset-table (done inside * calcSizeBinary) */ memset(&wg->bufferedMessage, 0, sizeof(UA_NetworkMessageOffsetBuffer)); - msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(&networkMessage, - &wg->bufferedMessage); + msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(&networkMessage, NULL); if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { UA_PubSubSecurityPolicy *sp = wg->config.securityPolicy; @@ -1677,13 +1676,10 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, return UA_STATUSCODE_BADNOTFOUND; /* Initialize variables so we can goto cleanup below */ - memset(ot, 0, sizeof(UA_PubSubOffsetTable)); - + UA_DataSetField *field = NULL; UA_NetworkMessage networkMessage; memset(&networkMessage, 0, sizeof(networkMessage)); - - UA_NetworkMessageOffsetBuffer oldOffsetTable; - memset(&oldOffsetTable, 0, sizeof(UA_NetworkMessageOffsetBuffer)); + memset(ot, 0, sizeof(UA_PubSubOffsetTable)); /* Validate the DataSetWriters and generate their DataSetMessage */ size_t msgSize; @@ -1710,8 +1706,7 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, /* Compute the message length and generate the old format offset-table (done * inside calcSizeBinary) */ - msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetBuffer(&networkMessage, - &oldOffsetTable); + msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(&networkMessage, ot); if(msgSize == 0) { res = UA_STATUSCODE_BADINTERNALERROR; goto cleanup; @@ -1722,114 +1717,49 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, if(res != UA_STATUSCODE_GOOD) goto cleanup; - /* Allocate memory for the output offset table */ - ot->offsets = (UA_PubSubOffset*) - UA_calloc(oldOffsetTable.offsetsSize, sizeof(UA_PubSubOffset)); - if(!ot->offsets) { - res = UA_STATUSCODE_BADOUTOFMEMORY; - goto cleanup; - } - - /* Walk over the (old) offset table and convert to the new schema. - * In parallel, pick up the component NodeIds. */ + /* Pick up the component NodeIds */ dsw = NULL; - size_t newo = 0; - UA_DataSetField *field = NULL; - for(size_t oldo = 0; oldo < oldOffsetTable.offsetsSize; oldo++) { - switch(oldOffsetTable.offsets[oldo].contentType) { - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); - newo++; + for(size_t i = 0; i < ot->offsetsSize; i++) { + UA_PubSubOffset *o = &ot->offsets[i]; + switch(o->offsetType) { + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: + case UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: + UA_NodeId_copy(&wg->head.identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_TIMESTAMP; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_PICOSECONDS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_NETWORKMESSAGE_GROUPVERSION; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&wg->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE: dsw = (dsw == NULL) ? LIST_FIRST(&wg->writers) : LIST_NEXT(dsw, listEntry); field = NULL; - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); - newo++; + /* fall through */ + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP: + case UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS: + UA_NodeId_copy(&dsw->head.identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_STATUS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_TIMESTAMP; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETMESSAGE_PICOSECONDS; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&dsw->head.identifier, &ot->offsets[newo].component); - newo++; - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE: field = (field == NULL) ? TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); - newo++; + UA_NodeId_copy(&field->identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT: field = (field == NULL) ? TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); - newo++; + UA_NodeId_copy(&field->identifier, &o->component); break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: + case UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW: field = (field == NULL) ? TAILQ_FIRST(&dsw->connectedDataSet->fields) : TAILQ_NEXT(field, listEntry); - ot->offsets[newo].offsetType = UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW; - ot->offsets[newo].offset = oldOffsetTable.offsets[oldo].offset; - UA_NodeId_copy(&field->identifier, &ot->offsets[newo].component); - newo++; + UA_NodeId_copy(&field->identifier, &o->component); break; default: break; } } - ot->offsetsSize = newo; - /* Clean up */ cleanup: - UA_NetworkMessageOffsetBuffer_clear(&oldOffsetTable); for(size_t i = 0; i < dsmCount; i++) { UA_DataSetMessage_clear(&dsmStore[i]); } From 956f170d77f15d7efc6626acad0f68cdddf168c6 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 22:43:11 +0100 Subject: [PATCH 142/158] refactor(pubsub): Remove old PubSub realtime implementation --- src/pubsub/ua_pubsub_connection.c | 22 +- src/pubsub/ua_pubsub_internal.h | 15 -- src/pubsub/ua_pubsub_reader.c | 69 ----- src/pubsub/ua_pubsub_readergroup.c | 115 -------- src/pubsub/ua_pubsub_writer.c | 2 +- src/pubsub/ua_pubsub_writergroup.c | 251 +----------------- tests/pubsub/check_pubsub_encryption.c | 6 +- tests/pubsub/check_pubsub_encryption_aes256.c | 4 +- tests/pubsub/check_pubsub_informationmodel.c | 8 - tests/pubsub/check_pubsub_mqtt.c | 7 +- .../check_pubsub_subscribe_msgrcvtimeout.c | 113 +------- 11 files changed, 25 insertions(+), 587 deletions(-) diff --git a/src/pubsub/ua_pubsub_connection.c b/src/pubsub/ua_pubsub_connection.c index 031947ddd..ef97751f8 100644 --- a/src/pubsub/ua_pubsub_connection.c +++ b/src/pubsub/ua_pubsub_connection.c @@ -280,29 +280,17 @@ UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_LOG_TRACE_PUBSUB(psm->logging, c, "Processing a received buffer"); /* Process RT ReaderGroups */ - UA_ReaderGroup *rg; UA_Boolean processed = false; - UA_ReaderGroup *nonRtRg = NULL; - LIST_FOREACH(rg, &c->readerGroups, listEntry) { - if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL && - rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL) - continue; - if(!(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE)) { - nonRtRg = rg; - continue; - } - processed |= UA_ReaderGroup_decodeAndProcessRT(psm, rg, msg); - } - - /* Any non-RT ReaderGroups? */ - if(!nonRtRg) + UA_ReaderGroup *rg = LIST_FIRST(&c->readerGroups); + /* Any interested ReaderGroups? */ + if(!rg) goto finish; /* Decode the received message for the non-RT ReaderGroups */ UA_StatusCode res; UA_NetworkMessage nm; memset(&nm, 0, sizeof(UA_NetworkMessage)); - if(nonRtRg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) { + if(rg->config.encodingMimeType == UA_PUBSUB_ENCODING_UADP) { res = UA_PubSubConnection_decodeNetworkMessage(psm, c, msg, &nm); } else { /* if(writerGroup->config.encodingMimeType == UA_PUBSUB_ENCODING_JSON) */ #ifdef UA_ENABLE_JSON_ENCODING @@ -326,8 +314,6 @@ UA_PubSubConnection_process(UA_PubSubManager *psm, UA_PubSubConnection *c, if(rg->head.state != UA_PUBSUBSTATE_OPERATIONAL && rg->head.state != UA_PUBSUBSTATE_PREOPERATIONAL) continue; - if(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) - continue; processed |= UA_ReaderGroup_process(psm, rg, &nm); } UA_NetworkMessage_clear(&nm); diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index bd80c81d7..48d9e7a88 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -371,7 +371,6 @@ struct UA_WriterGroup { UA_UInt32 writersCount; UA_UInt64 publishCallbackId; /* registered if != 0 */ - UA_NetworkMessageOffsetBuffer bufferedMessage; UA_UInt16 sequenceNumber; /* Increased after every sent message */ UA_Boolean configurationFrozen; UA_DateTime lastPublishTimeStamp; @@ -471,8 +470,6 @@ struct UA_DataSetReader { UA_DataSetReaderConfig config; UA_ReaderGroup *linkedReaderGroup; - UA_NetworkMessageOffsetBuffer bufferedMessage; - /* MessageReceiveTimeout handling */ UA_UInt64 msgRcvTimeoutTimerId; }; @@ -495,14 +492,6 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier, const UA_DataSetReaderConfig *dataSetReaderConfig, UA_NodeId *readerIdentifier); -UA_StatusCode -UA_DataSetReader_prepareOffsetBuffer(Ctx *ctx, UA_DataSetReader *reader, - UA_ByteString *buf); - -void -UA_DataSetReader_decodeAndProcessRT(UA_PubSubManager *psm, UA_DataSetReader *dsr, - UA_ByteString buf); - UA_StatusCode UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr); @@ -595,10 +584,6 @@ UA_StatusCode UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_PubSubState targetState); -UA_Boolean -UA_ReaderGroup_decodeAndProcessRT(UA_PubSubManager *psm, UA_ReaderGroup *rg, - UA_ByteString buf); - UA_Boolean UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_NetworkMessage *nm); diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index cc5718a54..c19e6aef3 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -302,7 +302,6 @@ UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr) { UA_LOG_INFO_PUBSUB(psm->logging, dsr, "DataSetReader deleted"); UA_DataSetReaderConfig_clear(&dsr->config); - UA_NetworkMessageOffsetBuffer_clear(&dsr->bufferedMessage); UA_PubSubComponentHead_clear(&dsr->head); UA_free(dsr); @@ -746,74 +745,6 @@ UA_DataSetReader_process(UA_PubSubManager *psm, UA_DataSetReader *dsr, } } -UA_StatusCode -UA_DataSetReader_prepareOffsetBuffer(Ctx *ctx, UA_DataSetReader *reader, - UA_ByteString *buf) { - UA_NetworkMessage *nm = (UA_NetworkMessage *) - UA_calloc(1, sizeof(UA_NetworkMessage)); - if(!nm) - return UA_STATUSCODE_BADOUTOFMEMORY; - - /* Decode using the non-rt decoding */ - UA_StatusCode rv = UA_NetworkMessage_decodeHeaders(ctx, nm); - if(rv != UA_STATUSCODE_GOOD) { - UA_NetworkMessage_clear(nm); - UA_free(nm); - return rv; - } - rv |= UA_NetworkMessage_decodePayload(ctx, nm); - rv |= UA_NetworkMessage_decodeFooters(ctx, nm); - if(rv != UA_STATUSCODE_GOOD) { - UA_NetworkMessage_clear(nm); - UA_free(nm); - return rv; - } - - /* Compute and store the offsets necessary to decode */ - size_t nmSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(nm, NULL); - if(nmSize == 0) { - UA_NetworkMessage_clear(nm); - UA_free(nm); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Set the offset buffer in the reader */ - reader->bufferedMessage.nm = nm; - return UA_STATUSCODE_GOOD; -} - -void -UA_DataSetReader_decodeAndProcessRT(UA_PubSubManager *psm, UA_DataSetReader *dsr, - UA_ByteString buf) { - /* Set up the decoding context */ - Ctx ctx; - ctx.pos = buf.data; - ctx.end = buf.data + buf.length; - ctx.depth = 0; - memset(&ctx.opts, 0, sizeof(UA_DecodeBinaryOptions)); - ctx.opts.customTypes = psm->sc.server->config.customDataTypes; - - UA_StatusCode rv; - if(!dsr->bufferedMessage.nm) { - /* This is the first message being received for the RT fastpath. - * Prepare the offset buffer. */ - rv = UA_DataSetReader_prepareOffsetBuffer(&ctx, dsr, &buf); - } else { - /* Decode with offset information and update the networkMessage */ - rv = UA_NetworkMessage_updateBufferedNwMessage(&ctx, &dsr->bufferedMessage); - } - if(rv != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "PubSub decoding failed. Could not decode with " - "status code %s.", UA_StatusCode_name(rv)); - return; - } - - UA_DataSetMessage *dsm = - dsr->bufferedMessage.nm->payload.dataSetPayload.dataSetMessages; - UA_DataSetReader_process(psm, dsr, dsm); -} - /**************/ /* Server API */ /**************/ diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index 98f8220c5..fcd1b9ff6 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -18,29 +18,6 @@ #include "ua_pubsub_keystorage.h" #endif -#define MALLOCMEMBUFSIZE 256 - -typedef struct { - size_t pos; - char buf[MALLOCMEMBUFSIZE]; -} MembufCalloc; - -static void * -membufCalloc(void *context, - size_t nelem, size_t elsize) { - if(nelem > MALLOCMEMBUFSIZE || elsize > MALLOCMEMBUFSIZE) - return NULL; - size_t total = nelem * elsize; - - MembufCalloc *mc = (MembufCalloc*)context; - if(mc->pos + total > MALLOCMEMBUFSIZE) - return NULL; - void *mem = mc->buf + mc->pos; - mc->pos += total; - memset(mem, 0, total); - return mem; -} - UA_ReaderGroup * UA_ReaderGroup_find(UA_PubSubManager *psm, const UA_NodeId id) { if(!psm) @@ -313,24 +290,12 @@ UA_ReaderGroup_freezeConfiguration(UA_PubSubManager *psm, UA_ReaderGroup *rg) { } } - /* Reset the OffsetBuffer. The OffsetBuffer for a frozen configuration is - * generated when the first message is received. So we know the exact - * settings which headers are present, etc. Until then the ReaderGroup is - * "PreOperational". */ - UA_NetworkMessageOffsetBuffer_clear(&dsr->bufferedMessage); return UA_STATUSCODE_GOOD; } static void UA_ReaderGroup_unfreezeConfiguration(UA_ReaderGroup *rg) { - if(!rg->configurationFrozen) - return; rg->configurationFrozen = false; - - UA_DataSetReader *dataSetReader; - LIST_FOREACH(dataSetReader, &rg->readers, listEntry) { - UA_NetworkMessageOffsetBuffer_clear(&dataSetReader->bufferedMessage); - } } UA_StatusCode @@ -560,79 +525,6 @@ UA_ReaderGroup_process(UA_PubSubManager *psm, UA_ReaderGroup *rg, return processed; } -UA_Boolean -UA_ReaderGroup_decodeAndProcessRT(UA_PubSubManager *psm, UA_ReaderGroup *rg, - UA_ByteString buf) { - /* Received a (first) message for the ReaderGroup. - * Transition from PreOperational to Operational. */ - rg->hasReceived = true; - if(rg->head.state == UA_PUBSUBSTATE_PREOPERATIONAL) - UA_ReaderGroup_setPubSubState(psm, rg, UA_PUBSUBSTATE_OPERATIONAL); - - /* Set up the decoding context */ - Ctx ctx; - ctx.pos = buf.data; - ctx.end = buf.data + buf.length; - ctx.depth = 0; - memset(&ctx.opts, 0, sizeof(UA_DecodeBinaryOptions)); - ctx.opts.customTypes = psm->sc.server->config.customDataTypes; - - UA_Boolean processed = false; - UA_NetworkMessage currentNetworkMessage; - memset(¤tNetworkMessage, 0, sizeof(UA_NetworkMessage)); - - /* Set the arena-based calloc for realtime processing. This is because the - * header can (in theory) contain PromotedFields that require - * heap-allocation. - * - * The arena is stack-allocated and rather small. So this method is - * reentrant. Typically the arena-based calloc is not used at all for the - * "static" network message headers encountered in an RT context. */ - MembufCalloc mc; - mc.pos = 0; - ctx.opts.callocContext = &mc; - ctx.opts.calloc = membufCalloc; - - UA_StatusCode rv = UA_NetworkMessage_decodeHeaders(&ctx, ¤tNetworkMessage); - if(rv != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, rg, - "PubSub receive. decoding headers failed"); - return false; - } - - /* Decrypt the message. Keep pos right after the header. */ - rv = verifyAndDecryptNetworkMessage(psm->logging, buf, &ctx, - ¤tNetworkMessage, rg); - if(rv != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, rg, "Subscribe failed. " - "Verify and decrypt network message failed."); - return false; - } - - /* Process the message for each reader */ - UA_DataSetReader *dsr; - LIST_FOREACH(dsr, &rg->readers, listEntry) { - /* Check if the reader is enabled */ - if(dsr->head.state != UA_PUBSUBSTATE_OPERATIONAL && - dsr->head.state != UA_PUBSUBSTATE_PREOPERATIONAL) - continue; - - /* Check the identifier */ - rv = UA_DataSetReader_checkIdentifier(psm, dsr, ¤tNetworkMessage); - if(rv != UA_STATUSCODE_GOOD) { - UA_LOG_DEBUG_PUBSUB(psm->logging, dsr, - "PubSub receive. Message intended for a different reader."); - continue; - } - - /* Process the message */ - UA_DataSetReader_decodeAndProcessRT(psm, dsr, buf); - processed = true; - } - - return processed; -} - /******************************/ /* Decrypt the NetworkMessage */ /******************************/ @@ -891,13 +783,6 @@ ReaderGroupChannelCallback(UA_ConnectionManager *cm, uintptr_t connectionId, return; } - /* ReaderGroup with realtime processing */ - if(rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) { - UA_ReaderGroup_decodeAndProcessRT(psm, rg, msg); - UA_UNLOCK(&server->serviceMutex); - return; - } - /* Decode message */ UA_NetworkMessage nm; memset(&nm, 0, sizeof(UA_NetworkMessage)); diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index e723fe06f..2a5b35da0 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -175,7 +175,7 @@ UA_DataSetWriter_create(UA_PubSubManager *psm, UA_PubSubState_isEnabled(wg->head.state)) { UA_LOG_WARNING_PUBSUB(psm->logging, wg, "Cannot add a DataSetWriter while the " - "WriterGroup with realtime options is enabled"); + "WriterGroup is enabled"); return UA_STATUSCODE_BADCONFIGURATIONERROR; } diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 5e6372444..82ca4c522 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -287,7 +287,6 @@ UA_WriterGroup_remove(UA_PubSubManager *psm, UA_WriterGroup *wg) { UA_LOG_INFO_PUBSUB(psm->logging, wg, "WriterGroup deleted"); UA_WriterGroupConfig_clear(&wg->config); - UA_NetworkMessageOffsetBuffer_clear(&wg->bufferedMessage); UA_PubSubComponentHead_clear(&wg->head); UA_free(wg); } @@ -299,142 +298,13 @@ UA_WriterGroup_remove(UA_PubSubManager *psm, UA_WriterGroup *wg) { static UA_StatusCode UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); - - if(wg->configurationFrozen) - return UA_STATUSCODE_GOOD; - - /* Freeze the WriterGroup */ wg->configurationFrozen = true; - - /* Offset table enabled? */ - if((wg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) == 0) - return UA_STATUSCODE_GOOD; - - /* Offset table only possible for binary encoding */ - if(wg->config.encodingMimeType != UA_PUBSUB_ENCODING_UADP) { - UA_LOG_WARNING_PUBSUB(psm->logging, wg, - "PubSub-RT configuration fail: Non-RT capable encoding."); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - - //TODO Clarify: should we only allow = maxEncapsulatedDataSetMessageCount == 1 with RT? - //TODO Clarify: Behaviour if the finale size is more than MTU - - /* Define variables here for goto */ - size_t msgSize; - UA_ByteString buf; - const UA_Byte *bufEnd; - UA_Byte *bufPos; - UA_NetworkMessage networkMessage; - UA_StatusCode res = UA_STATUSCODE_GOOD; - UA_STACKARRAY(UA_UInt16, dsWriterIds, wg->writersCount); - UA_STACKARRAY(UA_DataSetMessage, dsmStore, wg->writersCount); - - /* Validate the DataSetWriters and generate their DataSetMessage */ - size_t dsmCount = 0; - UA_DataSetWriter *dsw; - LIST_FOREACH(dsw, &wg->writers, listEntry) { - dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; - res = UA_DataSetWriter_prepareDataSet(psm, dsw, &dsmStore[dsmCount]); - if(res != UA_STATUSCODE_GOOD) - goto cleanup; - dsmCount++; - } - - /* Generate the NetworkMessage */ - memset(&networkMessage, 0, sizeof(networkMessage)); - res = generateNetworkMessage(wg->linkedConnection, wg, dsmStore, dsWriterIds, - (UA_Byte) dsmCount, &wg->config.messageSettings, - &wg->config.transportSettings, &networkMessage); - if(res != UA_STATUSCODE_GOOD) - goto cleanup; - - /* Compute the message length and generate the offset-table (done inside - * calcSizeBinary) */ - memset(&wg->bufferedMessage, 0, sizeof(UA_NetworkMessageOffsetBuffer)); - msgSize = UA_NetworkMessage_calcSizeBinaryWithOffsetTable(&networkMessage, NULL); - - if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { - UA_PubSubSecurityPolicy *sp = wg->config.securityPolicy; - msgSize += sp->symmetricModule.cryptoModule. - signatureAlgorithm.getLocalSignatureSize(sp->policyContext); - } - - /* Generate the buffer for the pre-encoded message */ - res = UA_ByteString_allocBuffer(&buf, msgSize); - if(res != UA_STATUSCODE_GOOD) - goto cleanup; - wg->bufferedMessage.buffer = buf; - - /* Encode the NetworkMessage */ - bufEnd = &wg->bufferedMessage.buffer.data[wg->bufferedMessage.buffer.length]; - bufPos = wg->bufferedMessage.buffer.data; - - /* Preallocate the encryption buffer */ - if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { - UA_Byte *payloadPosition; - UA_NetworkMessage_encodeBinaryWithEncryptStart(&networkMessage, &bufPos, bufEnd, - &payloadPosition); - wg->bufferedMessage.payloadPosition = payloadPosition; - wg->bufferedMessage.nm = (UA_NetworkMessage *)UA_calloc(1,sizeof(UA_NetworkMessage)); - wg->bufferedMessage.nm->securityHeader = networkMessage.securityHeader; - UA_ByteString_allocBuffer(&wg->bufferedMessage.encryptBuffer, msgSize); - } - - if(wg->config.securityMode <= UA_MESSAGESECURITYMODE_NONE) - UA_NetworkMessage_encodeBinaryWithEncryptStart(&networkMessage, &bufPos, bufEnd, NULL); - - /* Post-processing of the OffsetBuffer to set the external data source from - * the DataSetField configuration */ - if(wg->config.rtLevel & UA_PUBSUB_RT_DIRECT_VALUE_ACCESS) { - size_t fieldPos = 0; - LIST_FOREACH(dsw, &wg->writers, listEntry) { - UA_PublishedDataSet *pds = dsw->connectedDataSet; - if(!pds) - continue; - - /* Loop over all DataSetFields */ - UA_DataSetField *dsf; - TAILQ_FOREACH(dsf, &pds->fields, listEntry) { - UA_NetworkMessageOffsetType contentType; - /* Move forward to the next payload-type offset field */ - do { - fieldPos++; - contentType = wg->bufferedMessage.offsets[fieldPos].contentType; - } while(contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW); - UA_assert(fieldPos < wg->bufferedMessage.offsetsSize); - - if(!dsf->config.field.variable.rtValueSource.rtFieldSourceEnabled) - continue; - - /* Set the external value soure in the offset buffer */ - UA_DataValue_clear(&wg->bufferedMessage.offsets[fieldPos].content.value); - wg->bufferedMessage.offsets[fieldPos].content.externalValue = - dsf->config.field.variable.rtValueSource.staticValueSource; - - /* Update the content type to _EXTERNAL */ - wg->bufferedMessage.offsets[fieldPos].contentType = - (UA_NetworkMessageOffsetType)(contentType + 1); - } - } - } - - cleanup: - /* Clean up DataSetMessages */ - for(size_t i = 0; i < dsmCount; i++) { - UA_DataSetMessage_clear(&dsmStore[i]); - } - return res; + return UA_STATUSCODE_GOOD; } static void UA_WriterGroup_unfreezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { - if(!wg->configurationFrozen) - return; wg->configurationFrozen = false; - UA_NetworkMessageOffsetBuffer_clear(&wg->bufferedMessage); } UA_StatusCode @@ -943,114 +813,6 @@ sendNetworkMessageBinary(UA_PubSubManager *psm, UA_PubSubConnection *connection, return UA_STATUSCODE_GOOD; } -static void -sampleOffsetPublishingValues(UA_PubSubManager *psm, UA_WriterGroup *wg) { - size_t fieldPos = 0; - UA_DataSetWriter *dsw; - LIST_FOREACH(dsw, &wg->writers, listEntry) { - UA_PublishedDataSet *pds = dsw->connectedDataSet; - if(!pds) - continue; - - /* Loop over the fields */ - UA_DataSetField *dsf; - TAILQ_FOREACH(dsf, &pds->fields, listEntry) { - /* Get the matching offset table entry */ - UA_NetworkMessageOffsetType contentType; - do { - fieldPos++; - contentType = wg->bufferedMessage.offsets[fieldPos].contentType; - } while(contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW && - contentType != UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL); - - /* External data source is never sampled, but accessed directly in - * the encoding */ - if(contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL || - contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL || - contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL) - continue; - - /* Sample the value into the offset table */ - UA_DataValue *dfv = &wg->bufferedMessage.offsets[fieldPos].content.value; - UA_DataValue_clear(dfv); - UA_PubSubDataSetField_sampleValue(psm, dsf, dfv); - } - } -} - -static void -publishWithOffsets(UA_PubSubManager *psm, UA_WriterGroup *wg, - UA_PubSubConnection *connection) { - UA_assert(wg->configurationFrozen); - - /* Fixed size but no direct value access. Sample to get recent values into - * the offset buffer structure. */ - if((wg->config.rtLevel & UA_PUBSUB_RT_DIRECT_VALUE_ACCESS) == 0) - sampleOffsetPublishingValues(psm, wg); - - UA_StatusCode res = - UA_NetworkMessage_updateBufferedMessage(&wg->bufferedMessage); - - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_DEBUG_PUBSUB(psm->logging, wg, - "PubSub sending. Unknown field type."); - return; - } - - UA_ByteString *buf = &wg->bufferedMessage.buffer; - - /* Send the encrypted buffered message if PubSub encryption is enabled */ - if(wg->config.securityMode > UA_MESSAGESECURITYMODE_NONE) { - size_t sigSize = wg->config.securityPolicy->symmetricModule.cryptoModule. - signatureAlgorithm.getLocalSignatureSize(wg->securityPolicyContext); - - UA_Byte payloadOffset = (UA_Byte)(wg->bufferedMessage.payloadPosition - - wg->bufferedMessage.buffer.data); - memcpy(wg->bufferedMessage.encryptBuffer.data, - wg->bufferedMessage.buffer.data, - wg->bufferedMessage.buffer.length); - res = encryptAndSign(wg, wg->bufferedMessage.nm, - wg->bufferedMessage.encryptBuffer.data, - wg->bufferedMessage.encryptBuffer.data + payloadOffset, - wg->bufferedMessage.encryptBuffer.data + - wg->bufferedMessage.encryptBuffer.length - sigSize); - - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR_PUBSUB(psm->logging, wg, "PubSub Encryption failed"); - return; - } - - buf = &wg->bufferedMessage.encryptBuffer; - } - - UA_ConnectionManager *cm = connection->cm; - if(!cm) - return; - - /* Select the wg sendchannel if configured */ - uintptr_t sendChannel = connection->sendChannel; - if(wg->sendChannel != 0) - sendChannel = wg->sendChannel; - if(sendChannel == 0) { - UA_LOG_ERROR_PUBSUB(psm->logging, wg, "Cannot send, no open connection"); - return; - } - - /* Copy into the network buffer */ - UA_ByteString outBuf; - res = cm->allocNetworkBuffer(cm, sendChannel, &outBuf, buf->length); - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR_PUBSUB(psm->logging, wg, "PubSub message memory allocation failed"); - return; - } - memcpy(outBuf.data, buf->data, buf->length); - sendNetworkMessageBuffer(psm, wg, connection, sendChannel, &outBuf); -} - static void sendNetworkMessage(UA_PubSubManager *psm, UA_WriterGroup *wg, UA_PubSubConnection *connection, UA_DataSetMessage *dsm, UA_UInt16 *writerIds, UA_Byte dsmCount) { @@ -1087,25 +849,18 @@ UA_WriterGroup_publishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { UA_LOG_DEBUG_PUBSUB(psm->logging, wg, "Publish Callback"); + UA_LOCK(&psm->sc.server->serviceMutex); + /* Find the connection associated with the writer */ UA_PubSubConnection *connection = wg->linkedConnection; if(!connection) { UA_LOG_ERROR_PUBSUB(psm->logging, wg, "Publish failed. PubSubConnection invalid"); - UA_LOCK(&psm->sc.server->serviceMutex); UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_ERROR); UA_UNLOCK(&psm->sc.server->serviceMutex); return; } - /* Realtime path - update the buffer message and send directly */ - if(wg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) { - publishWithOffsets(psm, wg, connection); - return; - } - - UA_LOCK(&psm->sc.server->serviceMutex); - /* How many DSM can be sent in one NM? */ UA_Byte maxDSM = (UA_Byte)wg->config.maxEncapsulatedDataSetMessageCount; if(wg->config.maxEncapsulatedDataSetMessageCount > UA_BYTE_MAX) diff --git a/tests/pubsub/check_pubsub_encryption.c b/tests/pubsub/check_pubsub_encryption.c index 376194535..403879fe3 100644 --- a/tests/pubsub/check_pubsub_encryption.c +++ b/tests/pubsub/check_pubsub_encryption.c @@ -82,7 +82,6 @@ START_TEST(SinglePublishDataSetField) { writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; retVal |= UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup3); - retVal |= UA_Server_enableWriterGroup(server, writerGroup3); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_PublishedDataSetConfig pdsConfig; @@ -109,7 +108,7 @@ START_TEST(SinglePublishDataSetField) { memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); retVal |= UA_Server_addDataSetWriter(server, writerGroup3, publishedDataSet1, - &dataSetWriterConfig, &dataSetWriter1); + &dataSetWriterConfig, &dataSetWriter1); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKey}; @@ -118,6 +117,9 @@ START_TEST(SinglePublishDataSetField) { UA_Server_setWriterGroupEncryptionKeys(server, writerGroup3, 1, sk, ek, kn); + retVal |= UA_Server_enableAllPubSubComponents(server); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + UA_PubSubManager *psm = getPSM(server); UA_WriterGroup *wg = UA_WriterGroup_find(psm, writerGroup3); UA_WriterGroup_publishCallback(psm, wg); diff --git a/tests/pubsub/check_pubsub_encryption_aes256.c b/tests/pubsub/check_pubsub_encryption_aes256.c index 01fa1670a..59eaedd1c 100644 --- a/tests/pubsub/check_pubsub_encryption_aes256.c +++ b/tests/pubsub/check_pubsub_encryption_aes256.c @@ -83,7 +83,6 @@ START_TEST(SinglePublishDataSetField) { writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; retVal |= UA_Server_addWriterGroup(server, connection2, &writerGroupConfig, &writerGroup3); - retVal |= UA_Server_enableWriterGroup(server, writerGroup3); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); UA_PublishedDataSetConfig pdsConfig; @@ -118,6 +117,9 @@ START_TEST(SinglePublishDataSetField) { UA_Server_setWriterGroupEncryptionKeys(server, writerGroup3, 1, sk, ek, kn); + retVal |= UA_Server_enableAllPubSubComponents(server); + ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); + UA_PubSubManager *psm = getPSM(server); UA_WriterGroup *wg = UA_WriterGroup_find(psm, writerGroup3); UA_WriterGroup_publishCallback(psm, wg); diff --git a/tests/pubsub/check_pubsub_informationmodel.c b/tests/pubsub/check_pubsub_informationmodel.c index cc2d27350..1895a457c 100644 --- a/tests/pubsub/check_pubsub_informationmodel.c +++ b/tests/pubsub/check_pubsub_informationmodel.c @@ -137,11 +137,8 @@ static void setupBasicPubSubConfiguration(void){ addPublishedDataSet(UA_STRING("PublishedDataSet 1"), &publishedDataSet1); addPublishedDataSet(UA_STRING("PublishedDataSet 2"), &publishedDataSet2); addWriterGroup(connection1, UA_STRING("WriterGroup 1"), 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); addWriterGroup(connection1, UA_STRING("WriterGroup 2"), 100, &writerGroup2); - UA_Server_enableWriterGroup(server, writerGroup2); addWriterGroup(connection2, UA_STRING("WriterGroup 3"), 1000, &writerGroup3); - UA_Server_enableWriterGroup(server, writerGroup3); addDataSetWriter(writerGroup1, publishedDataSet1, UA_STRING("DataSetWriter 1"), &dataSetWriter1); addDataSetWriter(writerGroup1, publishedDataSet2, UA_STRING("DataSetWriter 2"), &dataSetWriter2); addDataSetWriter(writerGroup2, publishedDataSet2, UA_STRING("DataSetWriter 3"), &dataSetWriter3); @@ -237,7 +234,6 @@ START_TEST(AddSingleWriterGroupAndCheckInformationModelRepresentation){ addPublishedDataSet(pdsName, &publishedDataSet1); UA_String wgName = UA_STRING("WriterGroup 1"); addWriterGroup(connection1, wgName, 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); UA_QualifiedName browseName; ck_assert_int_eq(UA_Server_readBrowseName(server, writerGroup1, &browseName), UA_STATUSCODE_GOOD); ck_assert_int_eq(UA_String_equal(&browseName.name, &wgName), UA_TRUE); @@ -265,14 +261,12 @@ START_TEST(AddRemoveAddSingleWriterGroupAndCheckInformationModelRepresentation){ addPublishedDataSet(pdsName, &publishedDataSet1); UA_String wgName = UA_STRING("WriterGroup 1"); addWriterGroup(connection1, wgName, 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); UA_QualifiedName browseName; UA_StatusCode retVal; ck_assert_int_eq(UA_Server_removeWriterGroup(server, writerGroup1), UA_STATUSCODE_GOOD); retVal = UA_Server_readBrowseName(server, writerGroup1, &browseName); ck_assert_int_eq(retVal, UA_STATUSCODE_BADNODEIDUNKNOWN); addWriterGroup(connection1, wgName, 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); retVal = UA_Server_readBrowseName(server, writerGroup1, &browseName); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); ck_assert_int_eq(UA_String_equal(&browseName.name, &wgName), UA_TRUE); @@ -286,7 +280,6 @@ START_TEST(AddSingleDataSetWriterAndCheckInformationModelRepresentation){ addPublishedDataSet(pdsName, &publishedDataSet1); UA_String wgName = UA_STRING("WriterGroup 1"); addWriterGroup(connection1, wgName, 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); UA_String dswName = UA_STRING("DataSetWriter 1"); addDataSetWriter(writerGroup1, publishedDataSet1, dswName, &dataSetWriter1); UA_QualifiedName browseName; @@ -315,7 +308,6 @@ START_TEST(AddRemoveAddSingleDataSetWriterAndCheckInformationModelRepresentation addPublishedDataSet(pdsName, &publishedDataSet1); UA_String wgName = UA_STRING("WriterGroup 1"); addWriterGroup(connection1, wgName, 10, &writerGroup1); - UA_Server_enableWriterGroup(server, writerGroup1); UA_String dswName = UA_STRING("DataSetWriter 1"); addDataSetWriter(writerGroup1, publishedDataSet1, dswName, &dataSetWriter1); UA_QualifiedName browseName; diff --git a/tests/pubsub/check_pubsub_mqtt.c b/tests/pubsub/check_pubsub_mqtt.c index ec4dc1d88..e159e91ef 100644 --- a/tests/pubsub/check_pubsub_mqtt.c +++ b/tests/pubsub/check_pubsub_mqtt.c @@ -243,9 +243,6 @@ START_TEST(SinglePublishSubscribeDateTime){ &readerGroupIdent); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - retval = UA_Server_enableAllPubSubComponents(server); - ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - // add DataSetReader memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); readerConfig.name = UA_STRING("DataSet Reader 1"); @@ -261,7 +258,6 @@ START_TEST(SinglePublishSubscribeDateTime){ &subscribedDataSetIdent); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - // add SubscribedVariables UA_NodeId folderId; UA_String folderName = readerConfig.dataSetMetaData.name; @@ -322,6 +318,9 @@ START_TEST(SinglePublishSubscribeDateTime){ UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); + retval = UA_Server_enableAllPubSubComponents(server); + ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); + } END_TEST START_TEST(CreateReaderGroup) { diff --git a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c index 7b318198d..11c18577b 100644 --- a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c +++ b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c @@ -64,7 +64,6 @@ AddConnection(char *pName, UA_UInt32 PublisherId, UA_NodeId *opConnectionId) { ck_assert(UA_Server_addPubSubConnection(server, &connectionConfig, opConnectionId) == UA_STATUSCODE_GOOD); - UA_Server_enablePubSubConnection(server, *opConnectionId); } static void @@ -371,8 +370,6 @@ START_TEST(Test_basic) { UA_Duration PublishingInterval_Conn1WG1 = 100.0; AddWriterGroup(&ConnId_1, "Conn1_WG1", 1, PublishingInterval_Conn1WG1, &WGId_Conn1_WG1); - UA_Server_enableWriterGroup(server, WGId_Conn1_WG1); - UA_NodeId DsWId_Conn1_WG1_DS1; UA_NodeId_init(&DsWId_Conn1_WG1_DS1); UA_NodeId VarId_Conn1_WG1; @@ -402,43 +399,8 @@ START_TEST(Test_basic) { MessageReceiveTimeout, &VarId_Conn2_RG1_DSR1, &DSRId_Conn2_RG1_DSR1); - UA_PubSubState state; - /* check WriterGroup and DataSetWriter state */ - ck_assert(UA_Server_WriterGroup_getState(server, WGId_Conn1_WG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); - ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - /* set WriterGroup operational */ - ck_assert(UA_Server_enableWriterGroup(server, WGId_Conn1_WG1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, DsWId_Conn1_WG1_DS1) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_WriterGroup_getState(server, WGId_Conn1_WG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); - ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); - - ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); - - /* check ReaderGroup and DataSetReader state */ - ck_assert(UA_Server_ReaderGroup_getState(server, RGId_Conn2_RG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - - /* set ReaderGroup operational */ - ck_assert(UA_Server_enableReaderGroup(server, RGId_Conn2_RG1) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_ReaderGroup_getState(server, RGId_Conn2_RG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PREOPERATIONAL); - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - - /* enable the reader */ - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn2_RG1_DSR1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PREOPERATIONAL); + UA_Server_enableAllPubSubComponents(server); /* check that publish/subscribe works -> set some test values */ ValidatePublishSubscribe(VarId_Conn1_WG1, VarId_Conn2_RG1_DSR1, 10, 100, 3); @@ -447,6 +409,7 @@ START_TEST(Test_basic) { ValidatePublishSubscribe(VarId_Conn1_WG1, VarId_Conn2_RG1_DSR1, 44, 100, 3); + UA_PubSubState state; ck_assert(UA_Server_ReaderGroup_getState(server, RGId_Conn2_RG1, &state) == UA_STATUSCODE_GOOD); ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); @@ -581,24 +544,7 @@ START_TEST(Test_different_timeouts) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "check normal pubsub operation"); - /* set all writer- and readergroups to operational (this triggers the - * publish and subscribe callback) enable the readers first, because - * otherwise we receive something immediately and start the message receive - * timeout. If we do some other checks before triggering the - * server_run_iterate function, this could cause a timeout. */ - - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableReaderGroup(server, RGId_Conn1_RG1)); - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableReaderGroup(server, RGId_Conn2_RG1)); - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableWriterGroup(server, WGId_Conn1_WG1)); - - ServerDoProcess("Wait Writer", 15, 1); - - ServerDoProcess("1", (UA_UInt32) (PublishingInterval_Conn1_WG1+1), 2); - - /* enable the reader */ - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn1_RG1_DSR1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn2_RG1_DSR1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, DsWId_Conn1_WG1_DS1) == UA_STATUSCODE_GOOD); + UA_Server_enableAllPubSubComponents(server); ServerDoProcess("1", (UA_UInt32) (PublishingInterval_Conn1_WG1+1), 2); @@ -684,14 +630,7 @@ START_TEST(Test_wrong_timeout) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "set writer and reader to operational"); - /* set all writer- and readergroups to operational */ - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableWriterGroup(server, WGId_Conn1_WG1)); - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableDataSetWriter(server, DsWId_Conn1_WG1_DS1)); - // Run Server so that DSW timer is triggered to set DSW to operational - ServerDoProcess("Wait Writer", 15, 1); - - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableReaderGroup(server, RGId_Conn1_RG1)); - ck_assert(UA_STATUSCODE_GOOD == UA_Server_enableDataSetReader(server, DSRId_Conn1_RG1_DSR1)); + UA_Server_enableAllPubSubComponents(server); UA_PubSubState state = UA_PUBSUBSTATE_DISABLED; ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); @@ -755,17 +694,7 @@ START_TEST(Test_update_config) { const UA_UInt32 SleepTime = 50; const UA_UInt32 NoOfRunIterateCycles = 6; - UA_PubSubState state; - /* set WriterGroup operational */ - ck_assert(UA_Server_enableWriterGroup(server, WGId_Conn1_WG1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, DsWId_Conn1_WG1_DS1) == UA_STATUSCODE_GOOD); - - /* set ReaderGroup operational */ - ck_assert(UA_Server_enableReaderGroup(server, RGId_Conn1_RG1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn1_RG1_DSR1) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn1_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PREOPERATIONAL); + UA_Server_enableAllPubSubComponents(server); ServerDoProcess("1", SleepTime, NoOfRunIterateCycles); @@ -780,6 +709,7 @@ START_TEST(Test_update_config) { ServerDoProcess("2", SleepTime, NoOfRunIterateCycles); /* The Reader is disabled now, since the timer has run out */ + UA_PubSubState state; ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn1_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); ck_assert(state == UA_PUBSUBSTATE_ERROR); @@ -802,8 +732,6 @@ START_TEST(Test_update_config) { ServerDoProcess("5", SleepTime, NoOfRunIterateCycles); - ServerDoProcess("5", SleepTime, NoOfRunIterateCycles * 5); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "END: Test_update_config\n\n"); } END_TEST @@ -886,34 +814,7 @@ START_TEST(Test_fast_path) { ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); ck_assert(state == UA_PUBSUBSTATE_DISABLED); - /* Set WriterGroup operational */ - ck_assert(UA_Server_enableWriterGroup(server, WGId_Conn1_WG1) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_WriterGroup_getState(server, WGId_Conn1_WG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_OPERATIONAL); - ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - - ServerDoProcess("0", (UA_UInt32) (PublishingInterval_Conn1WG1), 3); - - ck_assert(UA_Server_DataSetWriter_getState(server, DsWId_Conn1_WG1_DS1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - - /* check ReaderGroup and DataSetReader state */ - ck_assert(UA_Server_ReaderGroup_getState(server, RGId_Conn2_RG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_DISABLED); - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn2_RG1_DSR1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PAUSED); - - /* Set ReaderGroup operational */ - ck_assert(UA_Server_enableReaderGroup(server, RGId_Conn2_RG1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_ReaderGroup_getState(server, RGId_Conn2_RG1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PREOPERATIONAL); - - ck_assert(UA_Server_enableDataSetReader(server, DSRId_Conn2_RG1_DSR1) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_DataSetReader_getState(server, DSRId_Conn2_RG1_DSR1, &state) == UA_STATUSCODE_GOOD); - ck_assert(state == UA_PUBSUBSTATE_PREOPERATIONAL); + UA_Server_enableAllPubSubComponents(server); ServerDoProcess("0", (UA_UInt32) (PublishingInterval_Conn1WG1), 1); From 7401d0d283f097c2075fbb3c0472b271a5935759 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Tue, 14 Jan 2025 22:46:24 +0100 Subject: [PATCH 143/158] refactor(pubsub): Remove unused old OffsetBuffer structure --- src/pubsub/ua_pubsub_networkmessage.h | 68 +------ src/pubsub/ua_pubsub_networkmessage_binary.c | 183 ------------------- 2 files changed, 4 insertions(+), 247 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage.h b/src/pubsub/ua_pubsub_networkmessage.h index c22e23c56..d1e528c7e 100644 --- a/src/pubsub/ua_pubsub_networkmessage.h +++ b/src/pubsub/ua_pubsub_networkmessage.h @@ -21,70 +21,6 @@ _UA_BEGIN_DECLS -/**********************************************/ -/* Network Message Offsets */ -/**********************************************/ - -/* Offsets for buffered messages in the PubSub fast path. */ -typedef enum { - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE, /* no content, marks the beginning of the DSM */ - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER, - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP, - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS, - UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS, - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER, - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_FIELDENCDODING, - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP, - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS, - UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_GROUPVERSION, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW, - UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL, - /* For subscriber RT */ - UA_PUBSUB_OFFSETTYPE_PUBLISHERID, - UA_PUBSUB_OFFSETTYPE_WRITERGROUPID, - UA_PUBSUB_OFFSETTYPE_DATASETWRITERID - /* Add more offset types as needed */ -} UA_NetworkMessageOffsetType; - -typedef struct { - UA_NetworkMessageOffsetType contentType; - union { - UA_UInt16 sequenceNumber; - UA_DataValue **externalValue; - UA_DataValue value; - } content; - size_t offset; -} UA_NetworkMessageOffset; - -typedef struct { - UA_ByteString buffer; /* The precomputed message buffer */ - UA_NetworkMessageOffset *offsets; /* Offsets for changes in the message buffer */ - size_t offsetsSize; - UA_NetworkMessage *nm; /* The precomputed NetworkMessage for subscriber */ - size_t rawMessageLength; - UA_ByteString encryptBuffer; /* The precomputed message buffer is copied - * into the encrypt buffer for encryption and - * signing*/ - UA_Byte *payloadPosition; /* Payload Position of the message to encrypt*/ -} UA_NetworkMessageOffsetBuffer; - -void -UA_NetworkMessageOffsetBuffer_clear(UA_NetworkMessageOffsetBuffer *nmob); - -UA_StatusCode -UA_NetworkMessage_updateBufferedMessage(UA_NetworkMessageOffsetBuffer *buffer); - -UA_StatusCode -UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffer *buffer); - -size_t -UA_NetworkMessage_calcSizeBinaryWithOffsetTable(const UA_NetworkMessage *p, - UA_PubSubOffsetTable *ot); - /** * DataSetMessage * ^^^^^^^^^^^^^^ */ @@ -113,6 +49,10 @@ void UA_DataSetMessage_clear(UA_DataSetMessage *p); * NetworkMessage Encoding * ^^^^^^^^^^^^^^^^^^^^^^^ */ +size_t +UA_NetworkMessage_calcSizeBinaryWithOffsetTable(const UA_NetworkMessage *p, + UA_PubSubOffsetTable *ot); + UA_StatusCode UA_NetworkMessage_encodeHeaders(const UA_NetworkMessage *src, UA_Byte **bufPos, const UA_Byte *bufEnd); diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index 869ca7c9e..dd9854a98 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -57,155 +57,6 @@ static UA_Boolean UA_NetworkMessage_ExtendedFlags1Enabled(const UA_NetworkMessag static UA_Boolean UA_NetworkMessage_ExtendedFlags2Enabled(const UA_NetworkMessage* src); static UA_Boolean UA_DataSetMessageHeader_DataSetFlags2Enabled(const UA_DataSetMessageHeader* src); -UA_StatusCode -UA_NetworkMessage_updateBufferedMessage(UA_NetworkMessageOffsetBuffer *buffer) { - UA_StatusCode rv = UA_STATUSCODE_GOOD; - const UA_Byte *bufEnd = &buffer->buffer.data[buffer->buffer.length]; - for(size_t i = 0; i < buffer->offsetsSize; ++i) { - UA_NetworkMessageOffset *nmo = &buffer->offsets[i]; - UA_Byte *bufPos = &buffer->buffer.data[nmo->offset]; - switch(nmo->contentType) { - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: - rv = UA_UInt16_encodeBinary(&nmo->content.sequenceNumber, &bufPos, bufEnd); - nmo->content.sequenceNumber++; - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: - rv = UA_DataValue_encodeBinary(&nmo->content.value, &bufPos, bufEnd); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE_EXTERNAL: - rv = UA_DataValue_encodeBinary(*nmo->content.externalValue, &bufPos, bufEnd); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: - rv = UA_Variant_encodeBinary(&nmo->content.value.value, &bufPos, bufEnd); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT_EXTERNAL: - rv = UA_Variant_encodeBinary(&(*nmo->content.externalValue)->value, &bufPos, bufEnd); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: - rv = UA_encodeBinaryInternal(nmo->content.value.value.data, - nmo->content.value.value.type, - &bufPos, &bufEnd, NULL, NULL, NULL); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW_EXTERNAL: - rv = UA_encodeBinaryInternal((*nmo->content.externalValue)->value.data, - (*nmo->content.externalValue)->value.type, - &bufPos, &bufEnd, NULL, NULL, NULL); - break; - default: - break; /* The other fields are assumed to not change between messages. - * Only used for RT decoding (not encoding). */ - } - } - return rv; -} - -UA_StatusCode -UA_NetworkMessage_updateBufferedNwMessage(Ctx *ctx, UA_NetworkMessageOffsetBuffer *buffer) { - /* The offset buffer was not prepared */ - UA_NetworkMessage *nm = buffer->nm; - if(!nm) - return UA_STATUSCODE_BADINTERNALERROR; - - /* The source string is too short */ - if((uintptr_t)(ctx->end - ctx->pos) < buffer->buffer.length) - return UA_STATUSCODE_BADDECODINGERROR; - - /* If this remains at UA_UINT32_MAX, then no raw fields are contained */ - const UA_Byte *smallestRawOffset = ctx->end; - UA_Byte *initialPos = ctx->pos; - - /* Considering one DSM in RT TODO: Clarify multiple DSM */ - UA_DataSetMessage* dsm = nm->payload.dataSetPayload.dataSetMessages; - - size_t payloadCounter = 0; - UA_DataSetMessageHeader header; - UA_StatusCode rv = UA_STATUSCODE_GOOD; - for(size_t i = 0; i < buffer->offsetsSize; ++i) { - ctx->pos = initialPos + buffer->offsets[i].offset; - switch(buffer->offsets[i].contentType) { - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_FIELDENCDODING: - memset(&header, 0, sizeof(UA_DataSetMessageHeader)); - rv = UA_DataSetMessageHeader_decodeBinary(ctx, &header); - break; - case UA_PUBSUB_OFFSETTYPE_PUBLISHERID: - switch(nm->publisherId.idType) { - case UA_PUBLISHERIDTYPE_BYTE: - rv = DECODE_BINARY(&nm->publisherId.id.byte, BYTE); - break; - case UA_PUBLISHERIDTYPE_UINT16: - rv = DECODE_BINARY(&nm->publisherId.id.uint16, UINT16); - break; - case UA_PUBLISHERIDTYPE_UINT32: - rv = DECODE_BINARY(&nm->publisherId.id.uint32, UINT32); - break; - case UA_PUBLISHERIDTYPE_UINT64: - rv = DECODE_BINARY(&nm->publisherId.id.uint64, UINT64); - break; - default: - /* UA_PUBLISHERIDTYPE_STRING is not supported because of - * UA_PUBSUB_RT_FIXED_SIZE */ - return UA_STATUSCODE_BADNOTSUPPORTED; - } - break; - case UA_PUBSUB_OFFSETTYPE_WRITERGROUPID: - rv = DECODE_BINARY(&nm->groupHeader.writerGroupId, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_DATASETWRITERID: - rv = DECODE_BINARY(&dsm->dataSetWriterId, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_SEQUENCENUMBER: - rv = DECODE_BINARY(&nm->groupHeader.sequenceNumber, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_TIMESTAMP: - rv = DECODE_BINARY(&nm->timestamp, DATETIME); - break; - case UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_PICOSECONDS: - rv = DECODE_BINARY(&nm->picoseconds, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_SEQUENCENUMBER: - rv = DECODE_BINARY(&dsm->header.dataSetMessageSequenceNr, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_STATUS: - rv = DECODE_BINARY(&dsm->header.status, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_TIMESTAMP: - rv = DECODE_BINARY(&dsm->header.timestamp, DATETIME); - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE_PICOSECONDS: - rv = DECODE_BINARY(&dsm->header.picoSeconds, UINT16); - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE: - UA_DataValue_clear(&dsm->data.keyFrameData.dataSetFields[payloadCounter]); - rv = DECODE_BINARY(&dsm->data.keyFrameData.dataSetFields[payloadCounter], DATAVALUE); - payloadCounter++; - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT: - UA_Variant_clear(&dsm->data.keyFrameData.dataSetFields[payloadCounter].value); - rv = DECODE_BINARY(&dsm->data.keyFrameData.dataSetFields[payloadCounter].value, VARIANT); - dsm->data.keyFrameData.dataSetFields[payloadCounter].hasValue = (rv == UA_STATUSCODE_GOOD); - payloadCounter++; - break; - case UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW: - /* We need only the start address of the raw fields */ - if(smallestRawOffset > ctx->pos) { - smallestRawOffset = ctx->pos; - dsm->data.keyFrameData.rawFields.data = ctx->pos; - dsm->data.keyFrameData.rawFields.length = buffer->rawMessageLength; - } - payloadCounter++; - break; - case UA_PUBSUB_OFFSETTYPE_DATASETMESSAGE: - break; /* Nothing to do */ - default: - return UA_STATUSCODE_BADNOTSUPPORTED; - } - UA_CHECK_STATUS(rv, return rv); - } - - return rv; -} - static UA_StatusCode UA_NetworkMessageHeader_encodeBinary(EncodeCtx *ctx, const UA_NetworkMessage *src) { @@ -1786,38 +1637,4 @@ UA_DataSetMessage_clear(UA_DataSetMessage* p) { memset(p, 0, sizeof(UA_DataSetMessage)); } -void -UA_NetworkMessageOffsetBuffer_clear(UA_NetworkMessageOffsetBuffer *nmob) { - UA_ByteString_clear(&nmob->buffer); - - if(nmob->nm) { - UA_NetworkMessage_clear(nmob->nm); - UA_free(nmob->nm); - } - - UA_ByteString_clear(&nmob->encryptBuffer); - - if(nmob->offsetsSize == 0) - return; - - for(size_t i = 0; i < nmob->offsetsSize; i++) { - UA_NetworkMessageOffset *offset = &nmob->offsets[i]; - if(offset->contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_VARIANT || - offset->contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_DATAVALUE || - offset->contentType == UA_PUBSUB_OFFSETTYPE_PAYLOAD_RAW) { - UA_DataValue_clear(&offset->content.value); - continue; - } - - if(offset->contentType == UA_PUBSUB_OFFSETTYPE_NETWORKMESSAGE_FIELDENCDODING) { - offset->content.value.value.data = NULL; - UA_DataValue_clear(&offset->content.value); - } - } - - UA_free(nmob->offsets); - - memset(nmob, 0, sizeof(UA_NetworkMessageOffsetBuffer)); -} - #endif /* UA_ENABLE_PUBSUB */ From e0ef113a84ff0c1bc407b28cdad7f2e39dcb9cd9 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 15 Jan 2025 17:30:41 +0100 Subject: [PATCH 144/158] refactor(pubsub): Remove old PubSubRTLevel enum --- .../server_pubsub_publish_rt_offsets.c | 1 - .../server_pubsub_publish_rt_state_machine.c | 1 - .../server_pubsub_subscribe_rt_offsets.c | 1 - ...server_pubsub_subscribe_rt_state_machine.c | 1 - include/open62541/server_pubsub.h | 41 +- src/pubsub/ua_pubsub_reader.c | 6 +- src/pubsub/ua_pubsub_readergroup.c | 77 -- src/pubsub/ua_pubsub_writer.c | 65 +- src/pubsub/ua_pubsub_writergroup.c | 14 - tests/CMakeLists.txt | 4 - tests/pubsub/check_pubsub_config_freeze.c | 5 - .../check_pubsub_custom_state_machine.c | 2 - .../pubsub/check_pubsub_encrypted_rt_levels.c | 781 ----------------- ...heck_pubsub_multiple_subscribe_rt_levels.c | 611 -------------- tests/pubsub/check_pubsub_offset.c | 2 - tests/pubsub/check_pubsub_publish_rt_levels.c | 588 ------------- tests/pubsub/check_pubsub_publisherid.c | 6 - .../check_pubsub_subscribe_config_freeze.c | 3 - .../check_pubsub_subscribe_msgrcvtimeout.c | 5 - .../pubsub/check_pubsub_subscribe_rt_levels.c | 787 ------------------ 20 files changed, 6 insertions(+), 2995 deletions(-) delete mode 100644 tests/pubsub/check_pubsub_encrypted_rt_levels.c delete mode 100644 tests/pubsub/check_pubsub_multiple_subscribe_rt_levels.c delete mode 100644 tests/pubsub/check_pubsub_publish_rt_levels.c delete mode 100644 tests/pubsub/check_pubsub_subscribe_rt_levels.c diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c index 9e011811d..5eefbf503 100644 --- a/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c +++ b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c @@ -86,7 +86,6 @@ int main(void) { writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; writerGroupConfig.writerGroupId = 100; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; /* Change message settings of writerGroup to send PublisherId, WriterGroupId * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c b/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c index c47343336..c78d52353 100644 --- a/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c +++ b/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c @@ -166,7 +166,6 @@ int main(void) { writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; writerGroupConfig.writerGroupId = 100; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; writerGroupConfig.customStateMachine = writerGroupStateMachine; /* Change message settings of writerGroup to send PublisherId, WriterGroupId diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c index ed087eae3..5cbfbcbea 100644 --- a/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c +++ b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c @@ -86,7 +86,6 @@ addReaderGroup(UA_Server *server) { UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); } diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c index 29bfde509..c6ffa590e 100644 --- a/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c +++ b/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c @@ -328,7 +328,6 @@ addReaderGroup(UA_Server *server) { UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); } diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index 244151a08..a5ca20e2d 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -436,43 +436,7 @@ UA_Server_removeDataSetField(UA_Server *server, const UA_NodeId dsfId); * container for :ref:`dsw` and network message settings. The WriterGroup can be * imagined as producer of the network messages. The creation of network * messages is controlled by parameters like the publish interval, which is e.g. - * contained in the WriterGroup. - * - * The message publishing can be configured for realtime requirements. The RT-levels - * go along with different requirements. The below listed levels can be configured: - * - * UA_PUBSUB_RT_NONE - * No realtime-specific configuration. - * - * UA_PUBSUB_RT_DIRECT_VALUE_ACCESS - * All PublishedDataSets need to point to a variable with a - * ``UA_VALUEBACKENDTYPE_EXTERNAL`` value backend. The value backend gets - * cached when the configuration is frozen. No lookup of the variable from - * the information is performed afterwards. This enables also big data - * structures to be updated atomically with a compare-and-switch operation on - * the ``UA_DataValue`` double-pointer in the backend. - * - * UA_PUBSUB_RT_FIXED_SIZE - * Validate that the message constains only fields with a known size. - * Then the message fields have fixed offsets that are known ahead of time. - * - * UA_PUBSUB_RT_DETERMINISTIC - * Both direct-access and fixed-size is being used. The server pre-allocates - * buffers when the configuration is frozen and uses only memcpy operations - * to update the PubSub network messages for sending. - * - * WARNING! For hard real time requirements the underlying system must be - * RT-capable. Also note that each PubSubConnection can have a dedicated - * EventLoop. That way normal client/server operations can run independently - * from PubSub. The double-pointer in the ``UA_VALUEBACKENDTYPE_EXTERNAL`` value - * backend allows avoid race-condition with non-blocking atomic operations. */ - -typedef enum { - UA_PUBSUB_RT_NONE = 0, - UA_PUBSUB_RT_DIRECT_VALUE_ACCESS = 1, - UA_PUBSUB_RT_FIXED_SIZE = 2, - UA_PUBSUB_RT_DETERMINISTIC = 3, -} UA_PubSubRTLevel; + * contained in the WriterGroup. */ typedef enum { UA_PUBSUB_ENCODING_UADP = 0, @@ -515,8 +479,6 @@ typedef struct { /* non std. config parameter. maximum count of embedded DataSetMessage in * one NetworkMessage */ UA_UInt16 maxEncapsulatedDataSetMessageCount; - /* non std. field */ - UA_PubSubRTLevel rtLevel; /* Security Configuration * Message are encrypted if a SecurityPolicy is configured and the @@ -836,7 +798,6 @@ typedef struct { UA_String name; /* non std. field */ - UA_PubSubRTLevel rtLevel; UA_KeyValueMap groupProperties; UA_PubSubEncodingType encodingMimeType; UA_ExtensionObject transportSettings; diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index c19e6aef3..982f118ab 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -133,8 +133,7 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier, if(!rg) return UA_STATUSCODE_BADNOTFOUND; - if(rg->config.rtLevel != UA_PUBSUB_RT_NONE && - UA_PubSubState_isEnabled(rg->head.state)) { + if(UA_PubSubState_isEnabled(rg->head.state)) { UA_LOG_WARNING_PUBSUB(psm->logging, rg, "Cannot add a DataSetReader while the " "ReaderGroup with realtime options is enabled"); @@ -270,8 +269,7 @@ UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr) { /* Check if the ReaderGroup is enabled with realtime options. Disallow * removal in that case. The RT path might still have a pointer to the * DataSetReader. Or we violate the fixed-size-message configuration.*/ - if(rg->config.rtLevel != UA_PUBSUB_RT_NONE && - UA_PubSubState_isEnabled(rg->head.state)) { + if(UA_PubSubState_isEnabled(rg->head.state)) { UA_LOG_WARNING_PUBSUB(psm->logging, dsr, "Removal of DataSetReader not possible while " "the ReaderGroup with realtime options is enabled"); diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index fcd1b9ff6..9da7eb33d 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -212,84 +212,7 @@ UA_ReaderGroup_remove(UA_PubSubManager *psm, UA_ReaderGroup *rg) { static UA_StatusCode UA_ReaderGroup_freezeConfiguration(UA_PubSubManager *psm, UA_ReaderGroup *rg) { UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); - - if(rg->configurationFrozen) - return UA_STATUSCODE_GOOD; - - /* ReaderGroup freeze */ rg->configurationFrozen = true; - - /* Not rt, we don't have to adjust anything */ - if((rg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) == 0) - return UA_STATUSCODE_GOOD; - - UA_DataSetReader *dsr; - UA_UInt16 dsrCount = 0; - LIST_FOREACH(dsr, &rg->readers, listEntry) { - dsrCount++; - } - if(dsrCount > 1) { - UA_LOG_WARNING_PUBSUB(psm->logging, rg, - "Multiple DSR in a readerGroup not supported in RT " - "fixed size configuration"); - return UA_STATUSCODE_BADNOTIMPLEMENTED; - } - - dsr = LIST_FIRST(&rg->readers); - - /* Support only to UADP encoding */ - if(dsr->config.messageSettings.content.decoded.type != - &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "PubSub-RT configuration fail: Non-RT capable encoding."); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - - /* Don't support string PublisherId for the fast-path (at this time) */ - if(dsr->config.publisherId.idType == UA_PUBLISHERIDTYPE_STRING) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "PubSub-RT configuration fail: String PublisherId"); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - - size_t fieldsSize = dsr->config.dataSetMetaData.fieldsSize; - for(size_t i = 0; i < fieldsSize; i++) { - /* TODO: Use the datasource from the node */ - /* UA_FieldTargetVariable *tv = */ - /* &dsr->config.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; */ - /* const UA_VariableNode *rtNode = (const UA_VariableNode *) */ - /* UA_NODESTORE_GET(server, &tv->targetVariable.targetNodeId); */ - /* if(!rtNode || */ - /* rtNode->valueBackend.backendType != UA_VALUEBACKENDTYPE_EXTERNAL) { */ - /* UA_LOG_WARNING_PUBSUB(server->config.logging, dsr, */ - /* "PubSub-RT configuration fail: PDS contains field " */ - /* "without external data source."); */ - /* UA_NODESTORE_RELEASE(server, (const UA_Node *) rtNode); */ - /* return UA_STATUSCODE_BADNOTSUPPORTED; */ - /* } */ - - /* /\* Set the external data source in the tv *\/ */ - /* tv->externalDataValue = rtNode->valueBackend.backend.external.value; */ - - /* UA_NODESTORE_RELEASE(server, (const UA_Node *) rtNode); */ - - UA_FieldMetaData *field = &dsr->config.dataSetMetaData.fields[i]; - if((UA_NodeId_equal(&field->dataType, &UA_TYPES[UA_TYPES_STRING].typeId) || - UA_NodeId_equal(&field->dataType, &UA_TYPES[UA_TYPES_BYTESTRING].typeId)) && - field->maxStringLength == 0) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "PubSub-RT configuration fail: " - "PDS contains String/ByteString with dynamic length."); - return UA_STATUSCODE_BADNOTSUPPORTED; - } else if(!UA_DataType_isNumeric(UA_findDataType(&field->dataType)) && - !UA_NodeId_equal(&field->dataType, &UA_TYPES[UA_TYPES_BOOLEAN].typeId)) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "PubSub-RT configuration fail: " - "PDS contains variable with dynamic size."); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - } - return UA_STATUSCODE_GOOD; } diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index 2a5b35da0..2b926344b 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -171,8 +171,7 @@ UA_DataSetWriter_create(UA_PubSubManager *psm, return UA_STATUSCODE_BADCONFIGURATIONERROR; } - if(wg->config.rtLevel != UA_PUBSUB_RT_NONE && - UA_PubSubState_isEnabled(wg->head.state)) { + if(UA_PubSubState_isEnabled(wg->head.state)) { UA_LOG_WARNING_PUBSUB(psm->logging, wg, "Cannot add a DataSetWriter while the " "WriterGroup is enabled"); @@ -189,19 +188,6 @@ UA_DataSetWriter_create(UA_PubSubManager *psm, "The PublishedDataSet was not found"); return UA_STATUSCODE_BADNOTFOUND; } - - if(wg->config.rtLevel != UA_PUBSUB_RT_NONE) { - UA_DataSetField *tmpDSF; - TAILQ_FOREACH(tmpDSF, &pds->fields, listEntry) { - if(!tmpDSF->config.field.variable.rtValueSource.rtFieldSourceEnabled && - !tmpDSF->config.field.variable.rtValueSource.rtInformationModelNode) { - UA_LOG_WARNING_PUBSUB(psm->logging, pds, - "Adding DataSetWriter failed: " - "Fields in PDS are not RT capable"); - return UA_STATUSCODE_BADCONFIGURATIONERROR; - } - } - } } UA_DataSetWriter *dsw = (UA_DataSetWriter *) @@ -292,15 +278,6 @@ UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw, UA_WriterGroup *wg = dsw->linkedWriterGroup; UA_assert(wg); - /* Promoted Fields not allowed if RT is enabled */ - if(wg->config.rtLevel > UA_PUBSUB_RT_NONE && - pds->promotedFieldsCount > 0) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "PDS contains promoted fields"); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - /* Test the DataSetFields */ UA_DataSetField *dsf; TAILQ_FOREACH(dsf, &pds->fields, listEntry) { @@ -318,39 +295,6 @@ UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw, return UA_STATUSCODE_BADNOTSUPPORTED; } UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *)rtNode); - - /* TODO: Get the External Value Source from the node instead of from the config */ - - /* If direct-value-access is enabled, the pointers need to be set */ - if(wg->config.rtLevel & UA_PUBSUB_RT_DIRECT_VALUE_ACCESS && - !dsf->config.field.variable.rtValueSource.rtFieldSourceEnabled) { - UA_LOG_ERROR_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: PDS published-variable " - "does not have an external data source"); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - - /* Check that the values have a fixed size if fixed offsets are needed */ - if(wg->config.rtLevel & UA_PUBSUB_RT_FIXED_SIZE) { - if((UA_NodeId_equal(&dsf->fieldMetaData.dataType, - &UA_TYPES[UA_TYPES_STRING].typeId) || - UA_NodeId_equal(&dsf->fieldMetaData.dataType, - &UA_TYPES[UA_TYPES_BYTESTRING].typeId)) && - dsf->fieldMetaData.maxStringLength == 0) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "PDS contains String/ByteString with dynamic length"); - return UA_STATUSCODE_BADNOTSUPPORTED; - } else if(!UA_DataType_isNumeric( - UA_findDataType(&dsf->fieldMetaData.dataType)) && - !UA_NodeId_equal(&dsf->fieldMetaData.dataType, - &UA_TYPES[UA_TYPES_BOOLEAN].typeId)) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "PDS contains variable with dynamic size"); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - } } /* Generate the DSM */ @@ -371,11 +315,8 @@ UA_DataSetWriter_remove(UA_PubSubManager *psm, UA_DataSetWriter *dsw) { UA_WriterGroup *wg = dsw->linkedWriterGroup; UA_assert(wg); - /* Check if the WriterGroup is enabled with RT options. Disallow removal in - * that case. The RT path might still have a pointer to the DataSetWriter. - * Or we violate the fixed-size-message configuration.*/ - if(wg->config.rtLevel != UA_PUBSUB_RT_NONE && - UA_PubSubState_isEnabled(wg->head.state)) { + /* Check if the WriterGroup is enabled. Disallow removal in that case. */ + if(UA_PubSubState_isEnabled(wg->head.state)) { UA_LOG_WARNING_PUBSUB(psm->logging, dsw, "Removal of DataSetWriter not possible while " "the WriterGroup with realtime options is enabled"); diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 82ca4c522..83bee12fd 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -906,15 +906,7 @@ UA_WriterGroup_publishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { sendNetworkMessage(psm, wg, connection, &dsmStore[dsmCount], &dsWriterIds[dsmCount], 1); - /* Clean up the current store entry */ - if(wg->config.rtLevel & UA_PUBSUB_RT_DIRECT_VALUE_ACCESS && - dsmStore[dsmCount].header.dataSetMessageType == UA_DATASETMESSAGE_DATAKEYFRAME) { - for(size_t i = 0; i < dsmStore[dsmCount].data.keyFrameData.fieldCount; ++i) { - dsmStore[dsmCount].data.keyFrameData.dataSetFields[i].value.data = NULL; - } - } UA_DataSetMessage_clear(&dsmStore[dsmCount]); - continue; /* Don't increase the dsmCount, reuse the slot */ } @@ -942,12 +934,6 @@ UA_WriterGroup_publishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { /* Clean up DSM */ for(size_t i = 0; i < dsmCount; i++) { - if(wg->config.rtLevel & UA_PUBSUB_RT_DIRECT_VALUE_ACCESS && - dsmStore[i].header.dataSetMessageType == UA_DATASETMESSAGE_DATAKEYFRAME) { - for(size_t j = 0; j < dsmStore[i].data.keyFrameData.fieldCount; ++j) { - dsmStore[i].data.keyFrameData.dataSetFields[j].value.data = NULL; - } - } UA_DataSetMessage_clear(&dsmStore[i]); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 711ca70ab..be1b15b20 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -272,10 +272,7 @@ if(UA_ENABLE_PUBSUB) ua_add_test(pubsub/check_pubsub_subscribe.c) ua_add_test(pubsub/check_pubsub_publishspeed.c) ua_add_test(pubsub/check_pubsub_config_freeze.c) - ua_add_test(pubsub/check_pubsub_publish_rt_levels.c) ua_add_test(pubsub/check_pubsub_subscribe_config_freeze.c) - ua_add_test(pubsub/check_pubsub_subscribe_rt_levels.c) - ua_add_test(pubsub/check_pubsub_multiple_subscribe_rt_levels.c) ua_add_test(pubsub/check_pubsub_offset.c) if(UA_ARCHITECTURE_POSIX) @@ -287,7 +284,6 @@ if(UA_ENABLE_PUBSUB) ua_add_test(pubsub/check_pubsub_encryption_aes256.c) ua_add_test(pubsub/check_pubsub_decryption.c) ua_add_test(pubsub/check_pubsub_subscribe_encrypted.c) - ua_add_test(pubsub/check_pubsub_encrypted_rt_levels.c) if(UA_ENABLE_PUBSUB_SKS) ua_add_test(pubsub/check_pubsub_sks_keystorage.c) ua_add_test(pubsub/check_pubsub_sks_push.c) diff --git a/tests/pubsub/check_pubsub_config_freeze.c b/tests/pubsub/check_pubsub_config_freeze.c index 1e7623807..a7ab43385 100644 --- a/tests/pubsub/check_pubsub_config_freeze.c +++ b/tests/pubsub/check_pubsub_config_freeze.c @@ -48,7 +48,6 @@ START_TEST(CreateAndLockConfiguration) { writerGroupConfig.name = UA_STRING("WriterGroup 1"); writerGroupConfig.publishingInterval = 10; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); UA_PublishedDataSetConfig pdsConfig; @@ -128,7 +127,6 @@ START_TEST(CreateAndLockConfigurationWithExternalAPI) { writerGroupConfig.name = UA_STRING("WriterGroup 1"); writerGroupConfig.publishingInterval = 10; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); UA_PublishedDataSetConfig pdsConfig; @@ -196,7 +194,6 @@ START_TEST(CreateAndReleaseMultiplePDSLocks) { writerGroupConfig.name = UA_STRING("WriterGroup 1"); writerGroupConfig.publishingInterval = 10; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); writerGroupConfig.name = UA_STRING("WriterGroup 2"); retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup2); @@ -271,7 +268,6 @@ START_TEST(CreateLockAndEditConfiguration) { writerGroupConfig.name = UA_STRING("WriterGroup 1"); writerGroupConfig.publishingInterval = 10; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); UA_PublishedDataSetConfig pdsConfig; @@ -337,7 +333,6 @@ START_TEST(CreateConfigWithStaticFieldSource) { writerGroupConfig.name = UA_STRING("WriterGroup 1"); writerGroupConfig.publishingInterval = 10; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); UA_PublishedDataSetConfig pdsConfig; diff --git a/tests/pubsub/check_pubsub_custom_state_machine.c b/tests/pubsub/check_pubsub_custom_state_machine.c index 0facdd3fa..42e1d87ec 100644 --- a/tests/pubsub/check_pubsub_custom_state_machine.c +++ b/tests/pubsub/check_pubsub_custom_state_machine.c @@ -173,7 +173,6 @@ START_TEST(CustomPublisher) { writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; writerGroupConfig.writerGroupId = 100; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; writerGroupConfig.customStateMachine = writerGroupStateMachine; /* Change message settings of writerGroup to send PublisherId, WriterGroupId @@ -491,7 +490,6 @@ addReaderGroup(UA_Server *server) { UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); } diff --git a/tests/pubsub/check_pubsub_encrypted_rt_levels.c b/tests/pubsub/check_pubsub_encrypted_rt_levels.c deleted file mode 100644 index d2ad53b46..000000000 --- a/tests/pubsub/check_pubsub_encrypted_rt_levels.c +++ /dev/null @@ -1,781 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2020 -2021 Kalycito Infotech Private Limited (Author: Keerthivasan) - */ - -#include -#include -#include "open62541/server_pubsub.h" -#include - -#include "ua_pubsub_internal.h" -#include "ua_pubsub_networkmessage.h" -#include "testing_clock.h" -#include "test_helpers.h" - -#include -#include -#include - -#include "testing_clock.h" - -UA_Server *server = NULL; -UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent, dataSetFieldIdent, dataSetFieldIdent1, readerGroupIdentifier, readerIdentifier; - -UA_UInt32 *subValue; -UA_DataValue *subDataValueRT; -UA_UInt32 *subValue1; -UA_DataValue *subDataValueRT1; -UA_NodeId subNodeId; -UA_NodeId subNodeId1; -UA_NodeId pubNodeId; -UA_NodeId pubNodeId1; -#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 -#define UA_AES128CTR_KEY_LENGTH 16 -#define UA_AES128CTR_KEYNONCE_LENGTH 4 - -UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; -UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; -UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; - -typedef struct { - UA_ByteString *buffer; -} UA_ReceiveContext; - -static UA_StatusCode -addMinimalPubSubConfiguration(void){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - /* Add one PubSubConnection */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = 2234; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); - if(retVal != UA_STATUSCODE_GOOD) - return retVal; - - /* Add one PublishedDataSet */ - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - /* Add one DataSetField to the PDS */ - UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); - return addResult.addResult; -} - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_ServerConfig *config = UA_Server_getConfig(server); - /* Instantiate the PubSub SecurityPolicy */ - config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) - UA_malloc(sizeof(UA_PubSubSecurityPolicy)); - config->pubSubConfig.securityPoliciesSize = 1; - UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], - config->logging); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -/* If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - if(UA_NodeId_equal(nodeId, &subNodeId)){ - memcpy(subValue, data->value.data, sizeof(UA_UInt32)); - } - - if(UA_NodeId_equal(nodeId, &subNodeId1)){ - memcpy(subValue1, data->value.data, sizeof(UA_UInt32)); - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -START_TEST(SetupInvalidPubSubConfig) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 100; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_ServerConfig *config = UA_Server_getConfig(server); - writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; - UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_VariableAttributes attributes = UA_VariableAttributes_default; - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - UA_Variant variant; - memset(&variant, 0, sizeof(UA_Variant)); - UA_Variant_setScalar(&variant, intValue, &UA_TYPES[UA_TYPES_UINT32]); - attributes.value = variant; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000), - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - attributes, NULL, NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000); - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - /* Not using static value source */ - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - /* UA_Server_addDataSetWriter fails because fields in PDS is not RT capable */ - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_BADCONFIGURATIONERROR); - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - // TODO security token not necessary for readergroup (extracted from security-header) - UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* DateTime DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_NodeId newnodeId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.dataType = UA_TYPES[UA_TYPES_DATETIME].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_NodeId readerIdentifier2; - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_Variant_clear(&variant); - - retVal = UA_Server_removeDataSetReader(server, readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(PublishAndSubscribeSingleFieldWithFixedOffsets) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 100; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_ServerConfig *config = UA_Server_getConfig(server); - writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; - UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published Int32"); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - /* Add dataset writer */ - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - // TODO security token not necessary for readergroup (extracted from security-header) - UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* UInt32 DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subValue = UA_UInt32_new(); - subDataValueRT = UA_DataValue_new(); - subDataValueRT->hasValue = UA_TRUE; - UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); - - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - - retVal = UA_Server_enableAllPubSubComponents(server); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - while(true) { - UA_fakeSleep(50); - UA_Server_run_iterate(server, false); - - /* Read data received by the Subscriber */ - UA_Variant *subscribedNodeData = UA_Variant_new(); - retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 50002), subscribedNodeData); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_Boolean eq = ((*(UA_Int32 *)subscribedNodeData->data) == 1000); - UA_Variant_clear(subscribedNodeData); - UA_free(subscribedNodeData); - if(eq) - break; - } - - UA_DataValue_delete(dataValue); - UA_free(subValue); - UA_free(subDataValueRT); -} END_TEST - - -START_TEST(PublishPDSWithMultipleFieldsAndSubscribeFixedSize) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - /* Add Subscribed Variables */ - UA_NodeId folderId1; - UA_String folderName1 = UA_STRING("PubNodes"); - UA_ObjectAttributes oAttr1 = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName1; - if (folderName1.length > 0) { - oAttr1.displayName.locale = UA_STRING ("en-US"); - oAttr1.displayName.text = folderName1; - folderBrowseName1.namespaceIndex = 1; - folderBrowseName1.name = folderName1; - } - else { - oAttr1.displayName = UA_LOCALIZEDTEXT ("en-US", "Published Variables"); - folderBrowseName1 = UA_QUALIFIEDNAME (1, "Published Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName1, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr1, NULL, &folderId1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - UA_UInt32 value = 0; - vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&vAttr.value, &value, &UA_TYPES[UA_TYPES_UINT32]); - vAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Published variable"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60000), - folderId1, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60001), - folderId1, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 DateTime"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &pubNodeId1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dataValue->hasValue = true; - dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published UInt32"); - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &dataValue; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60000), valueBackend); - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60000); - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, NULL).result == UA_STATUSCODE_GOOD); - - UA_DataSetFieldConfig dsfConfig1; - memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig)); - UA_UInt32 *intValue1 = UA_UInt32_new(); - *intValue1 = 2000; - UA_DataValue *dataValue1 = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue1->value, intValue1, &UA_TYPES[UA_TYPES_UINT32]); - dataValue->hasValue = true; - dsfConfig1.field.variable.fieldNameAlias = UA_STRING("Published1 UInt32"); - dsfConfig1.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend1; - valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend1.backend.external.value = &dataValue1; - valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, 60001), valueBackend1); - dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 60001); - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, NULL).result == UA_STATUSCODE_GOOD); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 100; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_ServerConfig *config = UA_Server_getConfig(server); - writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; - UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - // TODO security token not necessary for readergroup (extracted from security-header) - retVal = UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 2; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* UInt32 DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - UA_FieldMetaData_init(&pMetaData->fields[1]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[1].dataType); - pMetaData->fields[1].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[1].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60003), - folderId1, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subValue = UA_UInt32_new(); - subDataValueRT = UA_DataValue_new(); - subDataValueRT->hasValue = UA_TRUE; - UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); - /* Set the value backend of the above create node to 'external value source' */ - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); - - vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed1 UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 60004), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed1 UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subValue1 = UA_UInt32_new(); - subDataValueRT1 = UA_DataValue_new(); - subDataValueRT1->hasValue = UA_TRUE; - UA_Variant_setScalar(&subDataValueRT1->value, subValue1, &UA_TYPES[UA_TYPES_UINT32]); - /* Set the value backend of the above create node to 'external value source' */ - valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend1.backend.external.value = &subDataValueRT1; - valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeId1, valueBackend1); - - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2; - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) UA_calloc(2, sizeof(UA_FieldTargetVariable)); - UA_FieldTargetDataType_init(&targetVars[0].targetVariable); - targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60003); - UA_FieldTargetDataType_init(&targetVars[1].targetVariable); - targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, 60004); - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - - retVal = UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - UA_FieldTargetDataType_clear(&targetVars[0].targetVariable); - UA_FieldTargetDataType_clear(&targetVars[1].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - - retVal = UA_Server_enableAllPubSubComponents(server); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - while(true) { - UA_fakeSleep(50); - UA_Server_run_iterate(server, false); - - /* Read data received by the Subscriber */ - UA_Variant *subscribedNodeData = UA_Variant_new(); - retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60003), subscribedNodeData); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_Boolean eq = ((*(UA_Int32 *)subscribedNodeData->data) == 1000); - UA_Variant_clear(subscribedNodeData); - UA_free(subscribedNodeData); - if(eq) - break; - } - - /* Read data received by the Subscriber */ - UA_Variant *subscribedNodeData1 = UA_Variant_new(); - retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 60004), subscribedNodeData1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert((*(UA_UInt32 *)subscribedNodeData1->data) == 2000); - UA_Variant_clear(subscribedNodeData1); - UA_free(subscribedNodeData1); - UA_Server_deleteNode(server, pubNodeId1, UA_TRUE); - UA_NodeId_clear(&pubNodeId); - UA_Server_deleteNode(server, pubNodeId1, UA_TRUE); - UA_NodeId_clear(&pubNodeId1); - UA_Server_deleteNode(server, subNodeId, UA_TRUE); - UA_NodeId_clear(&subNodeId); - UA_Server_deleteNode(server, subNodeId1, UA_TRUE); - UA_NodeId_clear(&subNodeId1); - UA_free(subValue); - UA_free(subDataValueRT); - UA_free(subValue1); - UA_free(subDataValueRT1); - /* Free external data source */ - UA_free(intValue); - UA_free(dataValue); - /* Free external data source */ - UA_free(intValue1); - UA_free(dataValue1); -} END_TEST - -int main(void) { - TCase *tc_pubsub_encryption_rt = tcase_create("PubSub encryption RT with fixed offsets"); - tcase_add_checked_fixture(tc_pubsub_encryption_rt, setup, teardown); - tcase_add_test(tc_pubsub_encryption_rt, SetupInvalidPubSubConfig); - tcase_add_test(tc_pubsub_encryption_rt, PublishAndSubscribeSingleFieldWithFixedOffsets); - tcase_add_test(tc_pubsub_encryption_rt, PublishPDSWithMultipleFieldsAndSubscribeFixedSize); - - Suite *s = suite_create("PubSub encryption RT configuration levels"); - suite_add_tcase(s, tc_pubsub_encryption_rt); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/pubsub/check_pubsub_multiple_subscribe_rt_levels.c b/tests/pubsub/check_pubsub_multiple_subscribe_rt_levels.c deleted file mode 100644 index 3857b8da2..000000000 --- a/tests/pubsub/check_pubsub_multiple_subscribe_rt_levels.c +++ /dev/null @@ -1,611 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2020 Kalycito Infotech Private Limited - */ - -#include -#include -#include - -#include "ua_pubsub_internal.h" -#include "ua_pubsub_networkmessage.h" -#include "testing_clock.h" -#include "test_helpers.h" - -#include -#include -#include - -UA_Server *server = NULL; -UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, writerGroupIdent1, dataSetWriterIdent, dataSetWriterIdent1, dataSetFieldIdent, readerGroupIdentifier, readerIdentifier; - -UA_UInt32 *subValue; -UA_DataValue *subDataValueRT; -UA_NodeId subNodeId; - -static UA_StatusCode -addMinimalPubSubConfiguration(void){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - /* Add one PubSubConnection */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = 2234; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); - if(retVal != UA_STATUSCODE_GOOD) - return retVal; - - /* Add one PublishedDataSet */ - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - /* Add one DataSetField to the PDS */ - UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); - return addResult.addResult; -} - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -/* If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - if(UA_NodeId_equal(nodeId, &subNodeId)){ - memcpy(subValue, data->value.data, sizeof(UA_UInt32)); - } - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -START_TEST(SubscribeMultipleMessagesRT) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_PubSubManager *psm = getPSM(server); - UA_PubSubConnection *connection = UA_PubSubConnection_find(psm, connectionIdentifier); - ck_assert(connection); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup2"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent1) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - /* add data set writers */ - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 2"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent1, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent1) == UA_STATUSCODE_GOOD); - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* UInt32 DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subValue = UA_UInt32_new(); - *subValue = 0; - subDataValueRT = UA_DataValue_new(); - UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); - subDataValueRT->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); - - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i].targetVariable); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); - - while(true) { - UA_fakeSleep(50); - UA_Server_run_iterate(server, false); - - /* Read data received by the Subscriber */ - UA_Variant *subscribedNodeData = UA_Variant_new(); - retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 50002), subscribedNodeData); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_Boolean eq = ((*(UA_Int32 *)subscribedNodeData->data) == 1000); - UA_Variant_delete(subscribedNodeData); - if(eq) - break; - } - - UA_DataValue_delete(dataValue); - UA_free(subValue); - UA_free(subDataValueRT); -} END_TEST - -START_TEST(SubscribeMultipleMessagesWithoutRT) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_PubSubManager *psm = getPSM(server); - UA_PubSubConnection *connection = UA_PubSubConnection_find(psm, connectionIdentifier); - ck_assert(connection); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup2"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent1) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - /* add data set writers */ - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 2"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent1, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent1) == UA_STATUSCODE_GOOD); - - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* UInt32 DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_NodeId newnodeId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = newnodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); - UA_Server_run_iterate(server, false); - UA_DataValue_delete(dataValue); -} END_TEST - -START_TEST(SetupInvalidPubSubConfig) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_VariableAttributes attributes = UA_VariableAttributes_default; - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - UA_Variant variant; - memset(&variant, 0, sizeof(UA_Variant)); - UA_Variant_setScalar(&variant, intValue, &UA_TYPES[UA_TYPES_UINT32]); - attributes.value = variant; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000), - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - attributes, NULL, NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000); - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - /* Not using static value source */ - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* DateTime DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_NodeId newnodeId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.dataType = UA_TYPES[UA_TYPES_DATETIME].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = newnodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_NodeId readerIdentifier2; - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_Variant_clear(&variant); - - retVal = UA_Server_removeDataSetReader(server, readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -int main(void) { - TCase *tc_pubsub_subscribe_rt = tcase_create("PubSub RT subscribe receive multiple messages"); - tcase_add_checked_fixture(tc_pubsub_subscribe_rt, setup, teardown); - tcase_add_test(tc_pubsub_subscribe_rt, SubscribeMultipleMessagesRT); - tcase_add_test(tc_pubsub_subscribe_rt, SubscribeMultipleMessagesWithoutRT); - tcase_add_test(tc_pubsub_subscribe_rt, SetupInvalidPubSubConfig); - - Suite *s = suite_create("PubSub RT configuration levels"); - suite_add_tcase(s, tc_pubsub_subscribe_rt); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/pubsub/check_pubsub_offset.c b/tests/pubsub/check_pubsub_offset.c index ae6067d43..f2cbc465b 100644 --- a/tests/pubsub/check_pubsub_offset.c +++ b/tests/pubsub/check_pubsub_offset.c @@ -96,7 +96,6 @@ START_TEST(PublisherOffsets) { writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS; writerGroupConfig.writerGroupId = 100; writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; /* Change message settings of writerGroup to send PublisherId, WriterGroupId * in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */ @@ -200,7 +199,6 @@ addReaderGroup(UA_Server *server) { UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); } diff --git a/tests/pubsub/check_pubsub_publish_rt_levels.c b/tests/pubsub/check_pubsub_publish_rt_levels.c deleted file mode 100644 index b772cc504..000000000 --- a/tests/pubsub/check_pubsub_publish_rt_levels.c +++ /dev/null @@ -1,588 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2019 Fraunhofer IOSB (Author: Andreas Ebner) - */ - -#include -#include -#include - -#include "test_helpers.h" -#include "ua_pubsub_internal.h" -#include "ua_pubsub_networkmessage.h" -#include - -#include "testing_clock.h" - -#include -#include -#include - -UA_EventLoop *rtEventLoop = NULL; -UA_Server *server = NULL; -UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent, dataSetFieldIdent; - -UA_DataValue *staticSource1, *staticSource2; - -#define PUBLISH_INTERVAL 10 /* Publish interval*/ - -static UA_StatusCode -addMinimalPubSubConfiguration(void){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - /* Add one PubSubConnection */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - connectionConfig.eventLoop = rtEventLoop; - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); - if(retVal != UA_STATUSCODE_GOOD) - return retVal; - /* Add one PublishedDataSet */ - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - /* Add one DataSetField to the PDS */ - UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); - return addResult.addResult; -} - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); - - rtEventLoop = UA_EventLoop_new_POSIX(server->config.logging); - - /* Add the TCP connection manager */ - UA_ConnectionManager *tcpCM = - UA_ConnectionManager_new_POSIX_TCP(UA_STRING("tcp connection manager")); - rtEventLoop->registerEventSource(rtEventLoop, (UA_EventSource *)tcpCM); - - /* Add the UDP connection manager */ - UA_ConnectionManager *udpCM = - UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager")); - rtEventLoop->registerEventSource(rtEventLoop, (UA_EventSource *)udpCM); - - rtEventLoop->start(rtEventLoop); -} - -static void teardown(void) { - /* Teardown needs both the sever and the RT EventLoop. Because the RT - * EventLoop needs to close the PubSubConnection sockets. Before that the - * server cannot shutdown. So we first force the shutdown of the PubSub - * "ServerComponent" and iterate both EventLoops until that is done. */ - UA_LOCK(&server->serviceMutex); - UA_PubSubManager *psm = getPSM(server); - psm->sc.stop(&psm->sc); - UA_UNLOCK(&server->serviceMutex); - - while(psm->sc.state != UA_LIFECYCLESTATE_STOPPED) { - rtEventLoop->run(rtEventLoop, 100); - server->config.eventLoop->run(server->config.eventLoop, 100); - } - - /* Now shutdown the server */ - UA_Server_run_shutdown(server); - - /* Now stop the RT EventLoop */ - if(rtEventLoop->state != UA_EVENTLOOPSTATE_FRESH && - rtEventLoop->state != UA_EVENTLOOPSTATE_STOPPED) { - rtEventLoop->stop(rtEventLoop); - while(rtEventLoop->state != UA_EVENTLOOPSTATE_STOPPED) { - rtEventLoop->run(rtEventLoop, 100); - } - } - - UA_Server_delete(server); - rtEventLoop->logger = NULL; /* Don't access the logger that was removed with - the server */ - rtEventLoop->free(rtEventLoop); - - rtEventLoop = NULL; - server = NULL; - - /* Clean these up only after the server is done */ - if(staticSource1) { - UA_DataValue_delete(staticSource1); - staticSource1 = NULL; - } - if(staticSource2) { - UA_DataValue_delete(staticSource2); - staticSource2 = NULL; - } -} - -START_TEST(PublishSingleFieldWithStaticValueSource) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - /* Create Variant and configure as DataSetField source */ - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - staticSource1 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource1->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource1; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - /* DataSetWriter muste be added AFTER addDataSetField otherwize lastSamples will be uninitialized */ - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - UA_Server_run_iterate(server, false); -} END_TEST - -START_TEST(PublishSingleFieldWithDifferentBinarySizes) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Test WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - UA_StatusCode res = UA_Server_addWriterGroup(server, connectionIdentifier, - &writerGroupConfig, &writerGroupIdent); - ck_assert(res == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - /* Create Variant and configure as DataSetField source */ - UA_String stringValue = UA_STRING("12345"); - staticSource1 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource1->value, &stringValue, &UA_TYPES[UA_TYPES_STRING]); - staticSource1->value.storageType = UA_VARIANT_DATA_NODELETE; - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource1; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - UA_Server_run_iterate(server, false); - } END_TEST - -START_TEST(SetupInvalidPubSubConfigWithStaticValueSource) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Test WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - - UA_DataSetFieldConfig dataSetFieldConfig; - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime"); - dataSetFieldConfig.field.variable.promotedField = UA_FALSE; - dataSetFieldConfig.field.variable.publishParameters.publishedVariable = - UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); - dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - UA_Server_addDataSetField(server, publishedDataSetIdent, - &dataSetFieldConfig, &dataSetFieldIdent); - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_BADCONFIGURATIONERROR); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - } END_TEST - -START_TEST(PublishSingleFieldWithFixedOffsets) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - staticSource1 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource1->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource1; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - UA_fakeSleep(50 + 1); - UA_Server_run_iterate(server, true); - - /* run server - publisher and subscriber */ - UA_fakeSleep(PUBLISH_INTERVAL + 1); - rtEventLoop->run(rtEventLoop, 100); - UA_fakeSleep(PUBLISH_INTERVAL + 1); - rtEventLoop->run(rtEventLoop, 100); - - - UA_Server_run_iterate(server, false); -} END_TEST - -START_TEST(PublishPDSWithMultipleFieldsAndFixedOffset) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - staticSource1 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource1->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_FALSE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource1; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, NULL).result == UA_STATUSCODE_GOOD); - UA_UInt32 *intValue2 = UA_UInt32_new(); - *intValue2 = (UA_UInt32) 2000; - staticSource2 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource2->value, intValue2, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource2; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, NULL).result == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - UA_fakeSleep(50 + 1); - UA_Server_run_iterate(server, true); - UA_Server_run_iterate(server, false); -} END_TEST - -START_TEST(PublishSingleFieldInCustomCallback) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - staticSource1 = UA_DataValue_new(); - UA_Variant_setScalar(&staticSource1->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &staticSource1; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_enableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - UA_fakeSleep(50 + 1); - UA_Server_run_iterate(server, true); - - /* run server - publisher and subscriber */ - UA_fakeSleep(PUBLISH_INTERVAL + 1); - rtEventLoop->run(rtEventLoop, 100); - UA_fakeSleep(PUBLISH_INTERVAL + 1); - rtEventLoop->run(rtEventLoop, 100); - - - UA_Server_run_iterate(server, false); -} END_TEST - -static UA_StatusCode -simpleNotificationRead(UA_Server *srv, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -static UA_NodeId *nodes[3]; -static UA_NodeId *dsf[3]; -static UA_UInt32 *values[3]; -static UA_NodeId variableNodeId; -static UA_UInt32 *integerRTValue; - -static UA_StatusCode -externalDataWriteCallback(UA_Server *s, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - if(UA_NodeId_equal(nodeId, nodes[0])){ - memcpy(values[0], data->value.data, sizeof(UA_UInt32)); - } else if(UA_NodeId_equal(nodeId, nodes[1])){ - memcpy(values[1], data->value.data, sizeof(UA_UInt32)); - } else if(UA_NodeId_equal(nodeId, nodes[2])){ - memcpy(values[2], data->value.data, sizeof(UA_UInt32)); - } else if(UA_NodeId_equal(nodeId, &variableNodeId)){ - memcpy(integerRTValue, data->value.data, sizeof(UA_UInt32)); - } - return UA_STATUSCODE_GOOD; -} - -START_TEST(PubSubConfigWithInformationModelRTVariable) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - //add a new variable to the information model - UA_VariableAttributes attr = UA_VariableAttributes_default; - UA_UInt32 myInteger = 42; - UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]); - attr.description = UA_LOCALIZEDTEXT("en-US","test node"); - attr.displayName = UA_LOCALIZEDTEXT("en-US","test node"); - attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "test node"); - UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); - UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); - ck_assert_int_eq(UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, - parentReferenceNodeId, myIntegerName, - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, &variableNodeId), UA_STATUSCODE_GOOD); - ck_assert(!UA_NodeId_isNull(&variableNodeId)); - UA_Variant variant; - UA_Variant_init(&variant); - UA_Server_readValue(server, variableNodeId, &variant); - ck_assert(*((UA_UInt32 *)variant.data) == 42); - //use the added var in rt config - integerRTValue = UA_UInt32_new(); - *integerRTValue = 42; - UA_DataValue *externalValueSourceDataValue = UA_DataValue_new(); - UA_Variant_setScalar(&externalValueSourceDataValue->value, integerRTValue, &UA_TYPES[UA_TYPES_UINT32]); - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &externalValueSourceDataValue; - valueBackend.backend.external.callback.notificationRead = simpleNotificationRead; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - UA_Server_setVariableNode_valueBackend(server, variableNodeId, valueBackend); - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.publishParameters.publishedVariable = variableNodeId; - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - UA_NodeId dsfNodeId; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dsfNodeId).result == UA_STATUSCODE_GOOD); - //read and update static memory directly and read new value over the information model - UA_Variant_clear(&variant); - UA_Variant_init(&variant); - ck_assert(UA_Server_readValue(server, variableNodeId, &variant) == UA_STATUSCODE_GOOD); - ck_assert(*((UA_UInt32 *)variant.data) == 42); - *integerRTValue = *integerRTValue + 1; - UA_Variant_clear(&variant); - UA_Variant_init(&variant); - UA_Server_readValue(server, variableNodeId, &variant); - ck_assert(*((UA_UInt32 *)variant.data) == 43); - UA_Server_removeDataSetField(server, dsfNodeId); - UA_Variant_clear(&variant); - UA_Variant_init(&variant); - UA_Server_readValue(server, variableNodeId, &variant); - ck_assert(*((UA_UInt32 *)variant.data) == 43); - UA_DataValue_delete(externalValueSourceDataValue); - UA_Variant_clear(&variant); - } END_TEST - -START_TEST(PubSubConfigWithMultipleInformationModelRTVariables) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUBLISH_INTERVAL; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER; - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - UA_DataValue *externalValueSources[3]; - for (size_t i = 0; i < 3; ++i) { - nodes[i] = UA_NodeId_new(); - dsf[i] = UA_NodeId_new(); - UA_VariableAttributes attr = UA_VariableAttributes_default; - UA_UInt32 myInteger = (UA_UInt32) i; - UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]); - attr.description = UA_LOCALIZEDTEXT("en-US","test node"); - attr.displayName = UA_LOCALIZEDTEXT("en-US","test node"); - attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "test node"); - UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); - UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); - ck_assert(UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, - parentReferenceNodeId, myIntegerName, - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, nodes[i]) == UA_STATUSCODE_GOOD); - //use the added var in rt config - values[i] = UA_UInt32_new(); - *values[i] = (UA_UInt32) i; - externalValueSources[i] = UA_DataValue_new(); - externalValueSources[i]->hasValue = UA_TRUE; - UA_Variant_setScalar(&externalValueSources[i]->value, values[i], &UA_TYPES[UA_TYPES_UINT32]); - - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &externalValueSources[i]; - valueBackend.backend.external.callback.notificationRead = simpleNotificationRead; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - UA_Server_setVariableNode_valueBackend(server, *nodes[i], valueBackend); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.publishParameters.publishedVariable = *nodes[i]; - //dsfConfig.field.variable.rtFieldSourceEnabled = UA_TRUE; - //dsfConfig.field.variable.staticValueSource.value = variantRT; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, dsf[i]).result == UA_STATUSCODE_GOOD); - } - - ck_assert(UA_Server_enableWriterGroup(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - ck_assert(UA_Server_setWriterGroupDisabled(server, writerGroupIdent) == UA_STATUSCODE_GOOD); - - for (size_t j = 0; j < 3; ++j) { - UA_Variant variant; - UA_Variant_init(&variant); - ck_assert(UA_Server_readValue(server, *nodes[j], &variant) == UA_STATUSCODE_GOOD); - ck_assert_uint_eq(j, *((UA_UInt32 *)variant.data)); - UA_Server_removeDataSetField(server, *dsf[j]); - UA_NodeId_delete(dsf[j]); - UA_Variant_clear(&variant); - UA_Variant_init(&variant); - ck_assert(UA_Server_readValue(server, *nodes[j], &variant) == UA_STATUSCODE_GOOD); - ck_assert_uint_eq(j, *((UA_UInt32 *)variant.data)); - UA_Variant_clear(&variant); - UA_NodeId_delete(nodes[j]); - UA_DataValue_delete(externalValueSources[j]); - } - } END_TEST - -int main(void) { - TCase *tc_pubsub_rt_static_value_source = tcase_create("PubSub RT publish with static value sources"); - tcase_add_checked_fixture(tc_pubsub_rt_static_value_source, setup, teardown); - tcase_add_test(tc_pubsub_rt_static_value_source, PublishSingleFieldWithStaticValueSource); - tcase_add_test(tc_pubsub_rt_static_value_source, PublishSingleFieldWithDifferentBinarySizes); - tcase_add_test(tc_pubsub_rt_static_value_source, SetupInvalidPubSubConfigWithStaticValueSource); - tcase_add_test(tc_pubsub_rt_static_value_source, PubSubConfigWithInformationModelRTVariable); - tcase_add_test(tc_pubsub_rt_static_value_source, PubSubConfigWithMultipleInformationModelRTVariables); - - TCase *tc_pubsub_rt_fixed_offsets = tcase_create("PubSub RT publish with fixed offsets"); - tcase_add_checked_fixture(tc_pubsub_rt_fixed_offsets, setup, teardown); - tcase_add_test(tc_pubsub_rt_fixed_offsets, PublishSingleFieldWithFixedOffsets); - tcase_add_test(tc_pubsub_rt_fixed_offsets, PublishPDSWithMultipleFieldsAndFixedOffset); - tcase_add_test(tc_pubsub_rt_fixed_offsets, PublishSingleFieldInCustomCallback); - - Suite *s = suite_create("PubSub RT configuration levels"); - suite_add_tcase(s, tc_pubsub_rt_static_value_source); - suite_add_tcase(s, tc_pubsub_rt_fixed_offsets); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/pubsub/check_pubsub_publisherid.c b/tests/pubsub/check_pubsub_publisherid.c index 79d096c39..4d91a53d2 100644 --- a/tests/pubsub/check_pubsub_publisherid.c +++ b/tests/pubsub/check_pubsub_publisherid.c @@ -75,9 +75,6 @@ AddWriterGroup(UA_NodeId *pConnectionId, char *pName, (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - if (UseFastPath) { - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - } ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_addWriterGroup(server, *pConnectionId, &writerGroupConfig, opWriterGroupId)); UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); @@ -160,9 +157,6 @@ AddReaderGroup(UA_NodeId *pConnectionId, char *pName, UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING(pName); - if (UseFastPath) { - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - } ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_addReaderGroup(server, *pConnectionId, &readerGroupConfig, opReaderGroupId)); } diff --git a/tests/pubsub/check_pubsub_subscribe_config_freeze.c b/tests/pubsub/check_pubsub_subscribe_config_freeze.c index 822b0b075..96a8e8e30 100644 --- a/tests/pubsub/check_pubsub_subscribe_config_freeze.c +++ b/tests/pubsub/check_pubsub_subscribe_config_freeze.c @@ -45,7 +45,6 @@ START_TEST(CreateAndLockConfiguration) { UA_ReaderGroupConfig readerGroupConfig; memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); UA_PubSubManager *psm = getPSM(server); @@ -96,7 +95,6 @@ START_TEST(CreateAndReleaseMultipleLocks) { UA_ReaderGroupConfig readerGroupConfig; memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); readerGroupConfig.name = UA_STRING("ReaderGroup 2"); retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup2); @@ -151,7 +149,6 @@ START_TEST(CreateLockAndEditConfiguration) { UA_ReaderGroupConfig readerGroupConfig; memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_NONE; retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); UA_PubSubManager *psm = getPSM(server); diff --git a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c index 11c18577b..daffcdf7f 100644 --- a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c +++ b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c @@ -90,8 +90,6 @@ AddWriterGroup(UA_NodeId *pConnectionId, char *pName, UA_UInt32 WriterGroupId, UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - if(UseFastPath) - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; ck_assert(UA_Server_addWriterGroup(server, *pConnectionId, &writerGroupConfig, opWriterGroupId) == UA_STATUSCODE_GOOD); @@ -180,9 +178,6 @@ AddReaderGroup(UA_NodeId *pConnectionId, char *pName, UA_NodeId *opReaderGroupId UA_ReaderGroupConfig readerGroupConfig; memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING(pName); - if (UseFastPath) { - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - } ck_assert(UA_Server_addReaderGroup(server, *pConnectionId, &readerGroupConfig, opReaderGroupId) == UA_STATUSCODE_GOOD); } diff --git a/tests/pubsub/check_pubsub_subscribe_rt_levels.c b/tests/pubsub/check_pubsub_subscribe_rt_levels.c deleted file mode 100644 index bc0230cd5..000000000 --- a/tests/pubsub/check_pubsub_subscribe_rt_levels.c +++ /dev/null @@ -1,787 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2020 Kalycito Infotech Private Limited (Author: Suriya Narayanan) - */ - -#include -#include -#include -#include - -#include "ua_pubsub_internal.h" -#include "ua_pubsub_networkmessage.h" -#include "testing_clock.h" -#include "test_helpers.h" - -#include -#include -#include - -UA_Server *server = NULL; -UA_NodeId connectionIdentifier, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent, dataSetFieldIdent, readerGroupIdentifier, readerIdentifier; - -UA_UInt32 *subValue; -UA_DataValue *subDataValueRT; -UA_NodeId subNodeId; - -/* utility function to trigger server process loop and wait until pubsub callbacks are executed */ -static void ServerDoProcess( - const UA_UInt32 sleep_ms, /* use at least publishing interval */ - const UA_UInt32 noOfIterateCycles) -{ - UA_Server_run_iterate(server, true); - for (UA_UInt32 i = 0; i < noOfIterateCycles; i++) { - UA_fakeSleep(sleep_ms); - UA_Server_run_iterate(server, true); - } -} - -static UA_StatusCode -addMinimalPubSubConfiguration(void){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - /* Add one PubSubConnection */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = 2234; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); - if(retVal != UA_STATUSCODE_GOOD) - return retVal; - - /* Add one PublishedDataSet */ - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - /* Add one DataSetField to the PDS */ - UA_AddPublishedDataSetResult addResult = UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); - return addResult.addResult; -} - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -/* If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - if(UA_NodeId_equal(nodeId, &subNodeId)){ - memcpy(subValue, data->value.data, sizeof(UA_UInt32)); - } - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *serverLocal, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -START_TEST(SubscribeSingleFieldWithFixedOffsets) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - // Create Variant and configure as DataSetField source - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = 1000; - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - dsfConfig.field.variable.fieldNameAlias = UA_STRING("Published Int32"); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dataValue; - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - /* add dataset writer */ - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* Data Set Reader */ - UA_UInt16 publisherIdentifier = 2234; - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* UInt32 DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &subNodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subValue = UA_UInt32_new(); - subDataValueRT = UA_DataValue_new(); - subDataValueRT->hasValue = UA_TRUE; - UA_Variant_setScalar(&subDataValueRT->value, subValue, &UA_TYPES[UA_TYPES_UINT32]); - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeId, valueBackend); - - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); - - while(true) { - UA_fakeSleep(50); - UA_Server_run_iterate(server, false); - - /* Read data received by the Subscriber */ - UA_Variant *subscribedNodeData = UA_Variant_new(); - retVal = UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 50002), subscribedNodeData); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - UA_Boolean eq = ((*(UA_Int32 *)subscribedNodeData->data) == 1000); - UA_Variant_clear(subscribedNodeData); - UA_free(subscribedNodeData); - if(eq) - break; - } - UA_DataValue_delete(dataValue); - UA_free(subValue); - UA_free(subDataValueRT); - - ck_assert(UA_Server_disableDataSetWriter(server, dataSetWriterIdent) == UA_STATUSCODE_GOOD); - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, publishedDataSetIdent)); -} END_TEST - -START_TEST(SetupInvalidPubSubConfigReader) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_GOOD); - - // UA_free(intValue); - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - retVal = UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, - &readerGroupIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Data Set Reader */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 100; - readerConfig.dataSetWriterId = 62541; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* DateTime DataType */ - UA_FieldMetaData_init(&pMetaData->fields[0]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_NodeId newnodeId; - UA_String folderName = readerConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed DateTime"); - vAttr.dataType = UA_TYPES[UA_TYPES_DATETIME].typeId; - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50002), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed DateTime"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); - - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = subNodeId; - - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_NodeId readerIdentifier2; - retVal = UA_Server_addDataSetReader (server, readerGroupIdentifier, &readerConfig, - &readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); - - UA_FieldTargetDataType_clear(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - // UA_Variant_clear(&variant); - - retVal = UA_Server_removeDataSetReader(server, readerIdentifier2); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, publishedDataSetIdent)); - } END_TEST - -START_TEST(SetupInvalidPubSubConfig) { - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType *wgm = UA_UadpWriterGroupMessageDataType_new(); - wgm->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = wgm; - writerGroupConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - ck_assert(UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent) == UA_STATUSCODE_GOOD); - UA_UadpWriterGroupMessageDataType_delete(wgm); - - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - // Create Variant and configure as DataSetField source - UA_VariableAttributes attributes = UA_VariableAttributes_default; - UA_UInt32 *intValue = UA_UInt32_new(); - *intValue = (UA_UInt32) 1000; - UA_Variant variant; - memset(&variant, 0, sizeof(UA_Variant)); - UA_Variant_setScalar(&variant, intValue, &UA_TYPES[UA_TYPES_UINT32]); - attributes.value = variant; - ck_assert_int_eq(UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000), - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - attributes, NULL, NULL), UA_STATUSCODE_GOOD); - dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000); - dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - /* Not using static value source */ - ck_assert(UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent).result == UA_STATUSCODE_GOOD); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Test DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - ck_assert(UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent) == UA_STATUSCODE_BADCONFIGURATIONERROR); - - UA_Variant_clear(&variant); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, publishedDataSetIdent)); -} END_TEST - -/* additional SubscriberBeforeWriteCallback test data */ -#define NUMVARS 2 -static UA_UInt32 sSubscriberWriteValue[NUMVARS] = {0}; -static UA_NodeId sSubscribeWriteCb_TargetVar_Id[NUMVARS]; -static UA_DataValue subscriberDataValues[NUMVARS]; -static UA_DataValue *subscriberIndirectDataValues[NUMVARS]; - -static void PublishSubscribeWithWriteCallback_Helper( - UA_NodeId *publisherNode, - UA_UInt32 **publisherData, - UA_Boolean useRawEncoding) { - - /* test fast-path with subscriber write callback */ - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "PublishSubscribeWithWriteCallback_Helper(): useRawEncoding = %s", - (useRawEncoding == UA_TRUE) ? "true" : "false"); - - /* configure the connection */ - int i; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - ck_assert(addMinimalPubSubConfiguration() == UA_STATUSCODE_GOOD); - UA_PubSubManager *psm = getPSM(server); - UA_PubSubConnection *connection = UA_PubSubConnection_find(psm, connectionIdentifier); - ck_assert(connection != 0); - - /* Data Set Field */ - UA_DataSetFieldConfig dataSetFieldConfig; - UA_NodeId dataSetFieldIdents[NUMVARS]; - for (i = 0; i < NUMVARS; i++) { - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldConfig.field.variable.promotedField = UA_FALSE; - dataSetFieldConfig.field.variable.publishParameters.publishedVariable = publisherNode[i]; - dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - retVal = UA_Server_addDataSetField (server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdents[i]).result; - ck_assert(retVal == UA_STATUSCODE_GOOD); - } - /* Writer group */ - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup Test"); - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.publishingInterval = 2; - writerGroupConfig.writerGroupId = 1; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - /* Message settings in WriterGroup to include necessary headers */ - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - retVal |= UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* DataSetWriter */ - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter Test"); - dataSetWriterConfig.dataSetWriterId = 1; - dataSetWriterConfig.keyFrameCount = 10; - if (useRawEncoding) { - dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; - } else { - dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_NONE; - } - retVal |= UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* Reader Group */ - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING ("ReaderGroup Test"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC; - retVal |= UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); - - /* Data Set Reader */ - /* Parameters to filter received NetworkMessage */ - UA_DataSetReaderConfig readerConfig; - memset (&readerConfig, 0, sizeof (UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING ("DataSetReader Test"); - UA_UInt16 publisherIdentifier = 2234; - readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - readerConfig.publisherId.id.uint16 = publisherIdentifier; - readerConfig.writerGroupId = 1; - readerConfig.dataSetWriterId = 1; - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dsReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dsReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - dsReaderMessage->publishingInterval = writerGroupConfig.publishingInterval; - readerConfig.messageSettings.content.decoded.data = dsReaderMessage; - readerConfig.messageReceiveTimeout = writerGroupConfig.publishingInterval * 10; - if (useRawEncoding) { - readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW; - readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; - } else { - readerConfig.expectedEncoding = UA_PUBSUB_RT_UNKNOWN; /* is this a good default value */ - readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_NONE; - } - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init (pMetaData); - pMetaData->name = UA_STRING ("DataSet Test"); - /* Static definition of number of fields size to NUMVARS to create targetVariables */ - pMetaData->fieldsSize = NUMVARS; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new(pMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]); - for (i = 0; i < NUMVARS; i++) { - /* Unsigned Integer DataType */ - UA_FieldMetaData_init (&pMetaData->fields[i]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT32].typeId, - &pMetaData->fields[i].dataType); - pMetaData->fields[i].builtInType = UA_NS0ID_UINT32; - pMetaData->fields[i].valueRank = -1; /* scalar */ - } - retVal |= UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - UA_UadpDataSetReaderMessageDataType_delete(dsReaderMessage); - dsReaderMessage = 0; - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* Create TargetVariables */ - UA_FieldTargetVariable targetVar[NUMVARS]; - memset(&targetVar, 0, sizeof(targetVar)); - for (i = 0; i < NUMVARS; i++) { - UA_Variant_setScalar(&subscriberDataValues[i].value, &sSubscriberWriteValue[i], - &UA_TYPES[UA_TYPES_UINT32]); - subscriberIndirectDataValues[i] = &subscriberDataValues[i]; - subscriberDataValues[i].hasValue = true; - UA_FieldTargetDataType_init(&targetVar[i].targetVariable); - targetVar[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar[i].targetVariable.targetNodeId = sSubscribeWriteCb_TargetVar_Id[i]; - targetVar[i].externalDataValue = &subscriberIndirectDataValues[i]; - } - retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, - NUMVARS, targetVar); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - for (i = 0; i < NUMVARS; i++) { - UA_FieldTargetDataType_clear(&targetVar[i].targetVariable); - } - UA_free(pMetaData->fields); - - /* Iterate the main loop, so the connection gets really removed */ - UA_Server_run_iterate(server, false); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); - - for (i = 0; i < NUMVARS; i++) { - /* run server - publisher and subscriber */ - *(publisherData[i]) = 42 + i; - sSubscriberWriteValue[i] = 0; - } - ServerDoProcess((UA_UInt32) writerGroupConfig.publishingInterval, 3); - for (i = 0; i < NUMVARS; i++) { - /* check that subscriber write callback has been called - verify received value */ - ck_assert_uint_eq(*(publisherData[i]), sSubscriberWriteValue[i]); - - /* set new publisher data and test again */ - *(publisherData[i]) = 42 + NUMVARS + i; - sSubscriberWriteValue[i] = 0; - } - ServerDoProcess((UA_UInt32) writerGroupConfig.publishingInterval, 3); - for (i = 0; i < NUMVARS; i++) { - /* check that subscriber write callback has been called - verify received value */ - ck_assert_uint_eq(*(publisherData[i]), sSubscriberWriteValue[i]); - - /* set new publisher data and test again for checking buffered data handling */ - *(publisherData[i]) = 42 + 2*NUMVARS + i; - sSubscriberWriteValue[i] = 0; - } - ServerDoProcess((UA_UInt32) writerGroupConfig.publishingInterval, 3); - for (i = 0; i < NUMVARS; i++) { - ck_assert_uint_eq(*(publisherData[i]), sSubscriberWriteValue[i]); - } - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_setWriterGroupDisabled(server, writerGroupIdent)); - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_setReaderGroupDisabled(server, readerGroupIdentifier)); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePubSubConnection(server, connectionIdentifier)); - - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, publishedDataSetIdent)); - - UA_NodeId_clear(&connectionIdentifier); - UA_NodeId_clear(&publishedDataSetIdent); - UA_NodeId_clear(&writerGroupIdent); - UA_NodeId_clear(&dataSetWriterIdent); - for (i = 0; i < NUMVARS; i++) { - UA_NodeId_clear(&dataSetFieldIdents[i]); - } - UA_NodeId_clear(&readerGroupIdentifier); - UA_NodeId_clear(&readerIdentifier); -} - -START_TEST(PublishSubscribeWithWriteCallback) { - - int i; - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "PublishSubscribeWithWriteCallback() test start"); - - /* Create variables to publish integer data */ - UA_NodeId publisherNode[NUMVARS]; - UA_VariableAttributes attr = UA_VariableAttributes_default; - attr.description = UA_LOCALIZEDTEXT("en-US","Published Integer"); - attr.displayName = UA_LOCALIZEDTEXT("en-US","Published Integer"); - attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - - UA_DataValue *publisherDataValue[NUMVARS]; - UA_UInt32 *publisherData[NUMVARS]; - UA_StatusCode retVal; - UA_ValueBackend valueBackend; - - for (i = 0; i < NUMVARS; i++) { - publisherDataValue[i] = UA_DataValue_new(); - ck_assert(publisherDataValue[i] != 0); - publisherData[i] = UA_UInt32_new(); - ck_assert(publisherData[i] != 0); - *publisherData[i] = 42*(i+1); - UA_Variant_setScalar(&publisherDataValue[i]->value, publisherData[i], &UA_TYPES[UA_TYPES_UINT32]); - retVal = UA_Server_addVariableNode(server, - UA_NODEID_NUMERIC(1, 50001+i), - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Published Integer"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - attr, NULL, &publisherNode[i]); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* add external value backend for fast-path */ - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &publisherDataValue[i]; - retVal = UA_Server_setVariableNode_valueBackend(server, publisherNode[i], valueBackend); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - } - - /* Add Subscribed Variables */ - UA_NodeId folderId; - UA_String folderName = UA_STRING("Subscribed Variables"); - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if (folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal = UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - /* Variables to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32"); - vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - UA_DataValue *subscriberDataValue[NUMVARS]; - UA_UInt32 *subscriberData[NUMVARS]; - for (i = 0; i < NUMVARS; i++) { - retVal = UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50001 + NUMVARS + i), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed UInt32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &sSubscribeWriteCb_TargetVar_Id[i]); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - subscriberDataValue[i] = UA_DataValue_new(); - ck_assert(subscriberDataValue[i] != 0); - subscriberData[i] = UA_UInt32_new(); - ck_assert(subscriberData[i] != 0); - *subscriberData[i] = 0; - UA_Variant_setScalar(&subscriberDataValue[i]->value, subscriberData[i], &UA_TYPES[UA_TYPES_UINT32]); - - /* add external value backend for fast-path */ - memset(&valueBackend, 0, sizeof(valueBackend)); - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subscriberDataValue[i]; - retVal = UA_Server_setVariableNode_valueBackend(server, sSubscribeWriteCb_TargetVar_Id[i], valueBackend); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - } - - PublishSubscribeWithWriteCallback_Helper(publisherNode, publisherData, UA_FALSE); - PublishSubscribeWithWriteCallback_Helper(publisherNode, publisherData, UA_TRUE); - - /* cleanup */ - for (i = 0; i < NUMVARS; i++) { - UA_DataValue_delete(subscriberDataValue[i]); - subscriberDataValue[i] = 0; - UA_DataValue_delete(publisherDataValue[i]); - publisherDataValue[i] = 0; - } - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "PublishSubscribeWithWriteCallback() test end"); -} END_TEST - - -int main(void) { - TCase *tc_pubsub_subscribe_rt = tcase_create("PubSub RT subscribe with fixed offsets"); - tcase_add_checked_fixture(tc_pubsub_subscribe_rt, setup, teardown); - tcase_add_test(tc_pubsub_subscribe_rt, SetupInvalidPubSubConfig); - tcase_add_test(tc_pubsub_subscribe_rt, SetupInvalidPubSubConfigReader); - tcase_add_test(tc_pubsub_subscribe_rt, SubscribeSingleFieldWithFixedOffsets); - tcase_add_test(tc_pubsub_subscribe_rt, PublishSubscribeWithWriteCallback); - - Suite *s = suite_create("PubSub RT configuration levels"); - suite_add_tcase(s, tc_pubsub_subscribe_rt); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} From 5bf6f0c3e7d09b8f4e4daef4634f152795b72a96 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 15 Jan 2025 20:28:44 +0100 Subject: [PATCH 145/158] refactor(pubsub): Remove the old direct-access API The public Offset Table API should be used for realtime operations on the NetworkMessages instead. --- examples/pubsub/pubsub_subscribe_encrypted.c | 13 +- .../pubsub/pubsub_subscribe_encrypted_tpm.c | 12 +- .../pubsub_subscribe_encrypted_tpm_keystore.c | 12 +- ...erver_pubsub_subscribe_custom_monitoring.c | 11 +- .../sks/pubsub_subscribe_encrypted_sks.c | 14 +- .../pubsub/tutorial_pubsub_mqtt_subscribe.c | 12 +- examples/pubsub/tutorial_pubsub_subscribe.c | 25 ++- .../server_pubsub_publish_rt_offsets.c | 2 - .../server_pubsub_publish_rt_state_machine.c | 2 - .../server_pubsub_subscribe_rt_offsets.c | 57 +----- ...server_pubsub_subscribe_rt_state_machine.c | 133 ++----------- include/open62541/server_pubsub.h | 64 +------ src/pubsub/ua_pubsub_config.c | 31 +--- src/pubsub/ua_pubsub_dataset.c | 68 ++----- src/pubsub/ua_pubsub_internal.h | 17 +- src/pubsub/ua_pubsub_ns0.c | 42 ++--- src/pubsub/ua_pubsub_reader.c | 174 +++++------------- src/pubsub/ua_pubsub_readergroup.c | 22 +-- tests/pubsub/check_pubsub_config_freeze.c | 4 +- .../check_pubsub_custom_state_machine.c | 59 +----- tests/pubsub/check_pubsub_get_state.c | 26 +-- tests/pubsub/check_pubsub_mqtt.c | 14 +- tests/pubsub/check_pubsub_offset.c | 69 +------ tests/pubsub/check_pubsub_publisherid.c | 21 +-- tests/pubsub/check_pubsub_sks_client.c | 16 +- tests/pubsub/check_pubsub_subscribe.c | 154 +++++++--------- .../check_pubsub_subscribe_config_freeze.c | 12 +- .../pubsub/check_pubsub_subscribe_encrypted.c | 31 ++-- .../check_pubsub_subscribe_msgrcvtimeout.c | 12 +- tests/pubsub/check_pubsub_udp_unicast.c | 12 +- 30 files changed, 299 insertions(+), 842 deletions(-) diff --git a/examples/pubsub/pubsub_subscribe_encrypted.c b/examples/pubsub/pubsub_subscribe_encrypted.c index 388df8b8a..fd4756272 100644 --- a/examples/pubsub/pubsub_subscribe_encrypted.c +++ b/examples/pubsub/pubsub_subscribe_encrypted.c @@ -135,8 +135,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { * received DataSet fields and target Variables in the Subscriber AddressSpace. * The values subscribed from the Publisher are updated in the value field of these variables */ /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -152,18 +152,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/pubsub_subscribe_encrypted_tpm.c b/examples/pubsub/pubsub_subscribe_encrypted_tpm.c index 264aa1188..09e6235fe 100644 --- a/examples/pubsub/pubsub_subscribe_encrypted_tpm.c +++ b/examples/pubsub/pubsub_subscribe_encrypted_tpm.c @@ -142,8 +142,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { * received DataSet fields and target Variables in the Subscriber AddressSpace. * The values subscribed from the Publisher are updated in the value field of these variables */ /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -160,17 +160,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/pubsub_subscribe_encrypted_tpm_keystore.c b/examples/pubsub/pubsub_subscribe_encrypted_tpm_keystore.c index 44348f4dd..8d37d1366 100644 --- a/examples/pubsub/pubsub_subscribe_encrypted_tpm_keystore.c +++ b/examples/pubsub/pubsub_subscribe_encrypted_tpm_keystore.c @@ -178,8 +178,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { * received DataSet fields and target Variables in the Subscriber AddressSpace. * The values subscribed from the Publisher are updated in the value field of these variables */ /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -196,16 +196,12 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } retval = UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/server_pubsub_subscribe_custom_monitoring.c b/examples/pubsub/server_pubsub_subscribe_custom_monitoring.c index c9a340dd0..cf3179ef8 100644 --- a/examples/pubsub/server_pubsub_subscribe_custom_monitoring.c +++ b/examples/pubsub/server_pubsub_subscribe_custom_monitoring.c @@ -123,8 +123,8 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId); /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -141,15 +141,14 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); + UA_FieldTargetDataType_clear(&targetVars[i]); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c b/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c index e1292246a..05a9e36eb 100644 --- a/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c +++ b/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c @@ -141,9 +141,9 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) { * between received DataSet fields and target Variables in the Subscriber * AddressSpace. The values subscribed from the Publisher are updated in the value * field of these variables */ - /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)UA_calloc( - readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -160,16 +160,12 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) { UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/tutorial_pubsub_mqtt_subscribe.c b/examples/pubsub/tutorial_pubsub_mqtt_subscribe.c index ba11912a5..2051372f4 100644 --- a/examples/pubsub/tutorial_pubsub_mqtt_subscribe.c +++ b/examples/pubsub/tutorial_pubsub_mqtt_subscribe.c @@ -215,8 +215,8 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { * received DataSet fields and target Variables in the Subscriber AddressSpace. * The values subscribed from the Publisher are updated in the value field of these variables */ /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -233,17 +233,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub/tutorial_pubsub_subscribe.c b/examples/pubsub/tutorial_pubsub_subscribe.c index 56bbd03b9..14986bda1 100644 --- a/examples/pubsub/tutorial_pubsub_subscribe.c +++ b/examples/pubsub/tutorial_pubsub_subscribe.c @@ -117,15 +117,17 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES), folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId); -/** - * **TargetVariables** - * - * The SubscribedDataSet option TargetVariables defines a list of Variable mappings between - * received DataSet fields and target Variables in the Subscriber AddressSpace. - * The values subscribed from the Publisher are updated in the value field of these variables */ + /** + * **TargetVariables** + * + * The SubscribedDataSet option TargetVariables defines a list of Variable + * mappings between received DataSet fields and target Variables in the + * Subscriber AddressSpace. The values subscribed from the Publisher are + * updated in the value field of these variables */ + /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -143,16 +145,13 @@ addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { vAttr, NULL, &newNode); /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c index 5eefbf503..92bf5cb03 100644 --- a/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c +++ b/examples/pubsub_realtime/server_pubsub_publish_rt_offsets.c @@ -74,8 +74,6 @@ int main(void) { /* Add the DataSetField */ memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); dsfConfig.field.variable.publishParameters.publishedVariable = publishVariables[i]; - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); } diff --git a/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c b/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c index c78d52353..01c9ec7db 100644 --- a/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c +++ b/examples/pubsub_realtime/server_pubsub_publish_rt_state_machine.c @@ -154,8 +154,6 @@ int main(void) { for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { /* TODO: Point to a variable in the information model */ memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); } diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c index 5cbfbcbea..9d39072ce 100644 --- a/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c +++ b/examples/pubsub_realtime/server_pubsub_subscribe_rt_offsets.c @@ -27,17 +27,9 @@ static UA_String transportProfile = * the buffered NetworkMessage will only be updated. */ -UA_NodeId connectionIdentifier; -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; - UA_Server *server; - UA_DataSetReaderConfig readerConfig; - -/* Simulate a custom data sink (e.g. shared memory) */ -UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; +UA_NodeId connectionIdentifier, readerGroupIdentifier, readerIdentifier; /* Define MetaData for TargetVariables */ static void @@ -117,12 +109,11 @@ addSubscribedVariables (UA_Server *server) { /* Set the subscribed data to TargetVariable type */ readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; /* Create the TargetVariables with respect to DataSetMetaData fields */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = + readerConfig.subscribedDataSet.target.targetVariablesSize = readerConfig.dataSetMetaData.fieldsSize; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = - (UA_FieldTargetVariable *)UA_calloc( - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, - sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize, + sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -141,28 +132,10 @@ addSubscribedVariables (UA_Server *server) { UA_QUALIFIEDNAME(1, "Subscribed UInt32"), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - repeatedFieldValues[i] = 0; - repeatedDataValueRT[i] = UA_DataValue_new(); - UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], - &UA_TYPES[UA_TYPES_UINT32]); - repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; - repeatedDataValueRT[i]->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - memset(&valueBackend, 0, sizeof(UA_ValueBackend)); - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[i]; - UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); - - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(ftdt); - ftdt->attributeId = UA_ATTRIBUTEID_VALUE; - ftdt->targetNodeId = newnodeId; + UA_FieldTargetDataType *tv = &readerConfig.subscribedDataSet.target.targetVariables[i]; + tv->attributeId = UA_ATTRIBUTEID_VALUE; + tv->targetNodeId = newnodeId; } } @@ -200,14 +173,7 @@ addDataSetReader(UA_Server *server) { addSubscribedVariables(server); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - UA_FieldTargetDataType_clear(ftdt); - } - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_DataSetReaderConfig_clear(&readerConfig); UA_free(readerConfig.dataSetMetaData.fields); UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); } @@ -258,12 +224,7 @@ int main(int argc, char **argv) { } UA_Server_delete(server); - UA_PubSubOffsetTable_clear(&ot); - for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - UA_DataValue_delete(repeatedDataValueRT[i]); - } return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } - diff --git a/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c b/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c index c6ffa590e..0793e3995 100644 --- a/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c +++ b/examples/pubsub_realtime/server_pubsub_subscribe_rt_state_machine.c @@ -208,78 +208,6 @@ connectionStateMachine(UA_Server *server, const UA_NodeId componentId, return UA_STATUSCODE_GOOD; } -/* Simulate a custom data sink (e.g. shared memory) */ -UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; - -/* If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -static void -subscribeAfterWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId, - const UA_NodeId *readerGroupId, - const UA_NodeId *targetVariableId, - void *targetVariableContext, - UA_DataValue **externalDataValue) { - (void) server; - (void) dataSetReaderId; - (void) readerGroupId; - (void) targetVariableContext; - - assert(targetVariableId != 0); - assert(externalDataValue != 0); - - UA_String strId; - UA_String_init(&strId); - UA_NodeId_print(targetVariableId, &strId); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "subscribeAfterWriteCallback(): " - "WriteUpdate() for node Id = '%.*s'. New Value = %u", (UA_Int32) strId.length, strId.data, - *((UA_UInt32*) (**externalDataValue).value.data)); - UA_String_clear(&strId); -} - -/* Callback gets triggered before subscriber has received data received data - * hasn't been copied/handled yet */ -static void -subscribeBeforeWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId, - const UA_NodeId *readerGroupId, const UA_NodeId *targetVariableId, - void *targetVariableContext, UA_DataValue **externalDataValue) { - (void) server; - (void) dataSetReaderId; - (void) readerGroupId; - (void) targetVariableContext; - - assert(targetVariableId != 0); - assert(externalDataValue != 0); - - UA_String strId; - UA_String_init(&strId); - UA_NodeId_print(targetVariableId, &strId); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "subscribeBeforeWriteCallback(): " - "WriteUpdate() for node Id = '%.*s'", - (UA_Int32) strId.length, strId.data); - UA_String_clear(&strId); -} - /* Define MetaData for TargetVariables */ static void fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { @@ -359,12 +287,11 @@ addSubscribedVariables (UA_Server *server) { /* Set the subscribed data to TargetVariable type */ readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; /* Create the TargetVariables with respect to DataSetMetaData fields */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = + readerConfig.subscribedDataSet.target.targetVariablesSize = readerConfig.dataSetMetaData.fieldsSize; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = - (UA_FieldTargetVariable *)UA_calloc( - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, - sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize, + sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -383,33 +310,11 @@ addSubscribedVariables (UA_Server *server) { UA_QUALIFIEDNAME(1, "Subscribed UInt32"), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - repeatedFieldValues[i] = 0; - repeatedDataValueRT[i] = UA_DataValue_new(); - UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], - &UA_TYPES[UA_TYPES_UINT32]); - repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; - repeatedDataValueRT[i]->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[i]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); - - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(ftdt); - ftdt->attributeId = UA_ATTRIBUTEID_VALUE; - ftdt->targetNodeId = newnodeId; - /* set both before and after write callback to show the usage */ - tv->beforeWrite = subscribeBeforeWriteCallback; - tv->externalDataValue = &repeatedDataValueRT[i]; - tv->afterWrite = subscribeAfterWriteCallback; + UA_FieldTargetDataType *tv = + &readerConfig.subscribedDataSet.target.targetVariables[i]; + tv->attributeId = UA_ATTRIBUTEID_VALUE; + tv->targetNodeId = newnodeId; } } @@ -430,8 +335,10 @@ addDataSetReader(UA_Server *server) { readerConfig.dataSetWriterId = 62541; readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); + readerConfig.messageSettings.content.decoded.type = + &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; + UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = + UA_UadpDataSetReaderMessageDataType_new(); dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask) (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | @@ -447,15 +354,7 @@ addDataSetReader(UA_Server *server) { addSubscribedVariables(server); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - UA_FieldTargetDataType_clear(ftdt); - } - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); + UA_DataSetReaderConfig_clear(&readerConfig); UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); } @@ -495,12 +394,6 @@ int main(int argc, char **argv) { retval = UA_Server_runUntilInterrupt(server); UA_Server_delete(server); - pthread_join(listenThread, NULL); - - for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - UA_DataValue_delete(repeatedDataValueRT[i]); - } - return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index a5ca20e2d..1f96db6be 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -376,20 +376,10 @@ typedef struct { UA_Boolean promotedField; UA_PublishedVariableDataType publishParameters; - /* non std. field */ - struct { - UA_Boolean rtFieldSourceEnabled; - /* If the rtInformationModelNode is set, the nodeid in publishParameter - * must point to a node with external data source backend defined */ - UA_Boolean rtInformationModelNode; - /* TODO: Decide if suppress C++ warnings and use 'UA_DataValue * * const - * staticValueSource;' */ - UA_DataValue ** staticValueSource; - } rtValueSource; UA_UInt32 maxStringLength; UA_LocalizedText description; /* If dataSetFieldId is not set, the GUID will be generated on adding the - * field*/ + * field */ UA_Guid dataSetFieldId; } UA_DataSetVariableConfig; @@ -630,42 +620,11 @@ typedef enum { UA_PUBSUB_SDS_MIRROR } UA_SubscribedDataSetType; -typedef struct { - /* Standard-defined FieldTargetDataType */ - UA_FieldTargetDataType targetVariable; - - /* If realtime-handling is required, set this pointer non-NULL and it will be used - * to memcpy the value instead of using the Write service. - * If the beforeWrite method pointer is set, it will be called before a memcpy update - * to the value. But param externalDataValue already contains the new value. - * If the afterWrite method pointer is set, it will be called after a memcpy update - * to the value. */ - UA_DataValue **externalDataValue; - void *targetVariableContext; /* user-defined pointer */ - void (*beforeWrite)(UA_Server *server, - const UA_NodeId *readerIdentifier, - const UA_NodeId *readerGroupIdentifier, - const UA_NodeId *targetVariableIdentifier, - void *targetVariableContext, - UA_DataValue **externalDataValue); - void (*afterWrite)(UA_Server *server, - const UA_NodeId *readerIdentifier, - const UA_NodeId *readerGroupIdentifier, - const UA_NodeId *targetVariableIdentifier, - void *targetVariableContext, - UA_DataValue **externalDataValue); -} UA_FieldTargetVariable; - -typedef struct { - size_t targetVariablesSize; - UA_FieldTargetVariable *targetVariables; -} UA_TargetVariables; - typedef struct { UA_String name; UA_SubscribedDataSetType subscribedDataSetType; union { - /* datasetmirror is currently not implemented */ + /* DataSetMirror is currently not implemented */ UA_TargetVariablesDataType target; } subscribedDataSet; UA_DataSetMetaDataType dataSetMetaData; @@ -719,10 +678,9 @@ typedef struct { UA_ExtensionObject messageSettings; UA_ExtensionObject transportSettings; UA_SubscribedDataSetType subscribedDataSetType; - /* TODO UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */ union { - UA_TargetVariables subscribedDataSetTarget; - // UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror; + /* TODO: UA_SubscribedDataSetMirrorDataType subscribedDataSetMirror */ + UA_TargetVariablesDataType target; } subscribedDataSet; /* non std. fields */ UA_String linkedStandaloneSubscribedDataSetName; @@ -763,10 +721,8 @@ UA_EXPORT UA_StatusCode UA_THREADSAFE UA_Server_disableDataSetReader(UA_Server *server, const UA_NodeId dsrId); UA_EXPORT UA_StatusCode UA_THREADSAFE -UA_Server_setDataSetReaderTargetVariables(UA_Server *server, - const UA_NodeId dsrId, - size_t tvsSize, - const UA_FieldTargetVariable *tvs); +UA_Server_setDataSetReaderTargetVariables(UA_Server *server, const UA_NodeId dsrId, + size_t targetVariablesSize, const UA_FieldTargetDataType *targetVariables); /* Legacy API */ #define UA_Server_DataSetReader_getConfig(server, dsrId, config) \ @@ -786,13 +742,7 @@ UA_Server_setDataSetReaderTargetVariables(UA_Server *server, * DataSetReader. * * The RT-levels go along with different requirements. The below listed levels - * can be configured for a ReaderGroup. - * - * - UA_PUBSUB_RT_NONE: RT applied to this level - * - UA_PUBSUB_RT_FIXED_SIZE: Extends PubSub RT functionality and implements - * fast path message decoding in the Subscriber. Uses a buffered network - * message and only decodes the necessary offsets stored in an offset - * buffer. */ + * can be configured for a ReaderGroup. */ typedef struct { UA_String name; diff --git a/src/pubsub/ua_pubsub_config.c b/src/pubsub/ua_pubsub_config.c index 39cc8f94f..ead4d0bd4 100644 --- a/src/pubsub/ua_pubsub_config.c +++ b/src/pubsub/ua_pubsub_config.c @@ -517,33 +517,19 @@ addSubscribedDataSet(UA_PubSubManager *psm, const UA_NodeId dsReaderIdent, if(subscribedDataSet->content.decoded.type == &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) { - UA_TargetVariablesDataType *tmpTargetVars = (UA_TargetVariablesDataType*) + UA_TargetVariablesDataType *targetVars = (UA_TargetVariablesDataType*) subscribedDataSet->content.decoded.data; - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(tmpTargetVars->targetVariablesSize, sizeof(UA_FieldTargetVariable)); - - for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) { - UA_FieldTargetDataType_copy(&tmpTargetVars->targetVariables[index], - &targetVars[index].targetVariable); - } - UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dsReaderIdent); if(dsr) res = DataSetReader_createTargetVariables(psm, dsr, - tmpTargetVars->targetVariablesSize, - targetVars); + targetVars->targetVariablesSize, + targetVars->targetVariables); if(res != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(psm->logging, UA_LOGCATEGORY_PUBSUB, "[UA_PubSubManager_addSubscribedDataSet] " "create TargetVariables failed"); } - - for(size_t index = 0; index < tmpTargetVars->targetVariablesSize; index++) { - UA_FieldTargetDataType_clear(&targetVars[index].targetVariable); - } - - UA_free(targetVars); return res; } @@ -989,22 +975,21 @@ generateDataSetReaderDataType(const UA_DataSetReader *src, UA_TargetVariablesDataType *tmpTarget = UA_TargetVariablesDataType_new(); if(!tmpTarget) return UA_STATUSCODE_BADOUTOFMEMORY; - UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget, - &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]); - const UA_TargetVariables *targets = - &src->config.subscribedDataSet.subscribedDataSetTarget; + const UA_TargetVariablesDataType *targets = &src->config.subscribedDataSet.target; tmpTarget->targetVariables = (UA_FieldTargetDataType *) UA_calloc(targets->targetVariablesSize, sizeof(UA_FieldTargetDataType)); if(!tmpTarget->targetVariables) return UA_STATUSCODE_BADOUTOFMEMORY; tmpTarget->targetVariablesSize = targets->targetVariablesSize; - for(size_t i = 0; i < tmpTarget->targetVariablesSize; i++) { - res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i].targetVariable, + res |= UA_FieldTargetDataType_copy(&targets->targetVariables[i], &tmpTarget->targetVariables[i]); } + UA_ExtensionObject_setValue(&dst->subscribedDataSet, tmpTarget, + &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]); + return res; } diff --git a/src/pubsub/ua_pubsub_dataset.c b/src/pubsub/ua_pubsub_dataset.c index 2269fa962..e39faf1fe 100644 --- a/src/pubsub/ua_pubsub_dataset.c +++ b/src/pubsub/ua_pubsub_dataset.c @@ -121,12 +121,9 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds, const UA_DataSetVariableConfig *var = &field->config.field.variable; /* Set the field identifier */ - if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL)) - { + if(!UA_Guid_equal(&var->dataSetFieldId, &UA_GUID_NULL)) { fieldMetaData->dataSetFieldId = var->dataSetFieldId; - } - else - { + } else { fieldMetaData->dataSetFieldId = UA_PubSubManager_generateUniqueGuid(psm); } @@ -137,32 +134,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds, UA_StatusCode res = UA_String_copy(&var->fieldNameAlias, &fieldMetaData->name); UA_CHECK_STATUS(res, return res); - /* Static value source. ToDo after freeze PR, the value source must be - * checked (other behavior for static value source) */ - if(var->rtValueSource.rtFieldSourceEnabled && - !var->rtValueSource.rtInformationModelNode) { - const UA_DataValue *svs = *var->rtValueSource.staticValueSource; - if(svs->value.arrayDimensionsSize > 0) { - fieldMetaData->arrayDimensions = (UA_UInt32 *) - UA_calloc(svs->value.arrayDimensionsSize, sizeof(UA_UInt32)); - if(fieldMetaData->arrayDimensions == NULL) - return UA_STATUSCODE_BADOUTOFMEMORY; - memcpy(fieldMetaData->arrayDimensions, svs->value.arrayDimensions, - sizeof(UA_UInt32) * svs->value.arrayDimensionsSize); - } - fieldMetaData->arrayDimensionsSize = svs->value.arrayDimensionsSize; - - if(svs->value.type) - res = UA_NodeId_copy(&svs->value.type->typeId, &fieldMetaData->dataType); - UA_CHECK_STATUS(res, return res); - - //TODO collect value rank for the static field source - fieldMetaData->properties = NULL; - fieldMetaData->propertiesSize = 0; - fieldMetaData->fieldFlags = UA_DATASETFIELDFLAGS_NONE; - return UA_STATUSCODE_GOOD; - } - /* Set the Array Dimensions */ const UA_PublishedVariableDataType *pp = &var->publishParameters; UA_Variant value; @@ -180,8 +151,6 @@ generateFieldMetaData(UA_PubSubManager *psm, UA_PublishedDataSet *pds, UA_calloc(value.arrayDimensionsSize, sizeof(UA_UInt32)); if(!fieldMetaData->arrayDimensions) return UA_STATUSCODE_BADOUTOFMEMORY; - memcpy(fieldMetaData->arrayDimensions, value.arrayDimensions, - sizeof(UA_UInt32)*value.arrayDimensionsSize); } fieldMetaData->arrayDimensionsSize = value.arrayDimensionsSize; @@ -496,25 +465,13 @@ UA_PubSubDataSetField_sampleValue(UA_PubSubManager *psm, UA_DataSetField *field, UA_DataValue *value) { UA_PublishedVariableDataType *params = &field->config.field.variable.publishParameters; - /* Read the value */ - if(field->config.field.variable.rtValueSource.rtInformationModelNode) { - const UA_VariableNode *rtNode = (const UA_VariableNode *) - UA_NODESTORE_GET(psm->sc.server, ¶ms->publishedVariable); - *value = **rtNode->valueBackend.backend.external.value; - value->value.storageType = UA_VARIANT_DATA_NODELETE; - UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *) rtNode); - } else if(field->config.field.variable.rtValueSource.rtFieldSourceEnabled == false){ - UA_ReadValueId rvid; - UA_ReadValueId_init(&rvid); - rvid.nodeId = params->publishedVariable; - rvid.attributeId = params->attributeId; - rvid.indexRange = params->indexRange; - *value = readWithSession(psm->sc.server, &psm->sc.server->adminSession, - &rvid, UA_TIMESTAMPSTORETURN_BOTH); - } else { - *value = **field->config.field.variable.rtValueSource.staticValueSource; - value->value.storageType = UA_VARIANT_DATA_NODELETE; - } + UA_ReadValueId rvid; + UA_ReadValueId_init(&rvid); + rvid.nodeId = params->publishedVariable; + rvid.attributeId = params->attributeId; + rvid.indexRange = params->indexRange; + *value = readWithSession(psm->sc.server, &psm->sc.server->adminSession, + &rvid, UA_TIMESTAMPSTORETURN_BOTH); } UA_AddPublishedDataSetResult @@ -731,9 +688,10 @@ UA_SubscribedDataSetConfig_copy(const UA_SubscribedDataSetConfig *src, memcpy(dst, src, sizeof(UA_SubscribedDataSetConfig)); res = UA_DataSetMetaDataType_copy(&src->dataSetMetaData, &dst->dataSetMetaData); res |= UA_String_copy(&src->name, &dst->name); - res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target, - &dst->subscribedDataSet.target); - + if(src->subscribedDataSetType == UA_PUBSUB_SDS_TARGET) { + res |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target, + &dst->subscribedDataSet.target); + } if(res != UA_STATUSCODE_GOOD) UA_SubscribedDataSetConfig_clear(dst); return res; diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index 48d9e7a88..0bd32b4a9 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -495,27 +495,14 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier, UA_StatusCode UA_DataSetReader_remove(UA_PubSubManager *psm, UA_DataSetReader *dsr); -/* Copy the configuration of Target Variables */ -UA_StatusCode UA_TargetVariables_copy(const UA_TargetVariables *src, - UA_TargetVariables *dst); - -/* Clear the Target Variables configuration */ -void UA_TargetVariables_clear(UA_TargetVariables *subscribedDataSetTarget); - -/* Copy the configuration of Field Target Variables */ -UA_StatusCode UA_FieldTargetVariable_copy(const UA_FieldTargetVariable *src, - UA_FieldTargetVariable *dst); - UA_StatusCode DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr, - size_t targetVariablesSize, - const UA_FieldTargetVariable *targetVariables); + size_t targetsSize, const UA_FieldTargetDataType *targets); /* Returns an error reason if the target state is `Error` */ void UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, - UA_PubSubState targetState, - UA_StatusCode errorReason); + UA_PubSubState targetState, UA_StatusCode errorReason); /**********************************************/ /* ReaderGroup */ diff --git a/src/pubsub/ua_pubsub_ns0.c b/src/pubsub/ua_pubsub_ns0.c index 1b012b8b2..837c69a6a 100644 --- a/src/pubsub/ua_pubsub_ns0.c +++ b/src/pubsub/ua_pubsub_ns0.c @@ -522,7 +522,6 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId, if(!psm) return UA_STATUSCODE_BADINTERNALERROR; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; UA_ExtensionObject *eoTargetVar = &dataSetReader->subscribedDataSet; if(eoTargetVar->encoding != UA_EXTENSIONOBJECT_DECODED || eoTargetVar->content.decoded.type != &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) @@ -531,6 +530,10 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId, const UA_TargetVariablesDataType *targetVars = (UA_TargetVariablesDataType*)eoTargetVar->content.decoded.data; + UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dataSetReaderId); + if(!dsr) + return UA_STATUSCODE_BADINTERNALERROR; + UA_NodeId folderId; UA_String folderName = pMetaData->name; UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; @@ -556,40 +559,25 @@ addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId, * Subscriber AddressSpace. The values subscribed from the Publisher are * updated in the value field of these variables */ - /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVarsData = (UA_FieldTargetVariable *) - UA_calloc(targetVars->targetVariablesSize, sizeof(UA_FieldTargetVariable)); + /* Add variable for the fields */ for(size_t i = 0; i < targetVars->targetVariablesSize; i++) { - /* Prepare the output structure */ - UA_FieldTargetDataType_init(&targetVarsData[i].targetVariable); - targetVarsData[i].targetVariable.attributeId = targetVars->targetVariables[i].attributeId; - - /* Add variable for the field */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; vAttr.description = pMetaData->fields[i].description; vAttr.displayName.locale = UA_STRING(""); vAttr.displayName.text = pMetaData->fields[i].name; vAttr.dataType = pMetaData->fields[i].dataType; UA_QualifiedName varname = {1, pMetaData->fields[i].name}; - retVal |= addNode(server, UA_NODECLASS_VARIABLE, - targetVars->targetVariables[i].targetNodeId, folderId, - UA_NS0ID(HASCOMPONENT), varname, UA_NS0ID(BASEDATAVARIABLETYPE), - &vAttr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], - NULL, &targetVarsData[i].targetVariable.targetNodeId); + addNode(server, UA_NODECLASS_VARIABLE, + targetVars->targetVariables[i].targetNodeId, folderId, + UA_NS0ID(HASCOMPONENT), varname, UA_NS0ID(BASEDATAVARIABLETYPE), + &vAttr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], + NULL, &targetVars->targetVariables[i].targetNodeId); + } - } - UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dataSetReaderId); - if(dsr) { - retVal = DataSetReader_createTargetVariables(psm, dsr, - targetVars->targetVariablesSize, - targetVarsData); - } else { - retVal = UA_STATUSCODE_BADINTERNALERROR; - } - for(size_t j = 0; j < targetVars->targetVariablesSize; j++) - UA_FieldTargetDataType_clear(&targetVarsData[j].targetVariable); - UA_free(targetVarsData); - return retVal; + /* Set the TargetVariables in the DSR config */ + return DataSetReader_createTargetVariables(psm, dsr, + targetVars->targetVariablesSize, + targetVars->targetVariables); } /** diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index 982f118ab..18b9a0509 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -205,25 +205,12 @@ UA_DataSetReader_create(UA_PubSubManager *psm, UA_NodeId readerGroupIdentifier, &dsr->config.dataSetMetaData); /* Prepare the input for _createTargetVariables and call it */ - UA_TargetVariablesDataType *tvs = &sds->config.subscribedDataSet.target; - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(tvs->targetVariablesSize, sizeof(UA_FieldTargetVariable)); - for(size_t i = 0; i < tvs->targetVariablesSize; i++) { - UA_FieldTargetDataType_copy(&tvs->targetVariables[i], - &targetVars[i].targetVariable); - } - + UA_TargetVariablesDataType *tvs = + &sds->config.subscribedDataSet.target; DataSetReader_createTargetVariables(psm, dsr, tvs->targetVariablesSize, - targetVars); + tvs->targetVariables); - /* Clean up the temporary array */ - for(size_t i = 0; i < tvs->targetVariablesSize; i++) { - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); - } - UA_free(targetVars); - - /* Set the backpointer */ - sds->connectedReader = dsr; + sds->connectedReader = dsr; /* Set the backpointer */ /* Make the connection visible in the information model */ #ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL @@ -325,8 +312,8 @@ UA_DataSetReaderConfig_copy(const UA_DataSetReaderConfig *src, &dst->linkedStandaloneSubscribedDataSetName); if(src->subscribedDataSetType == UA_PUBSUB_SDS_TARGET) { - ret |= UA_TargetVariables_copy(&src->subscribedDataSet.subscribedDataSetTarget, - &dst->subscribedDataSet.subscribedDataSetTarget); + ret |= UA_TargetVariablesDataType_copy(&src->subscribedDataSet.target, + &dst->subscribedDataSet.target); } if(ret != UA_STATUSCODE_GOOD) @@ -344,7 +331,7 @@ UA_DataSetReaderConfig_clear(UA_DataSetReaderConfig *cfg) { UA_ExtensionObject_clear(&cfg->messageSettings); UA_ExtensionObject_clear(&cfg->transportSettings); if(cfg->subscribedDataSetType == UA_PUBSUB_SDS_TARGET) { - UA_TargetVariables_clear(&cfg->subscribedDataSet.subscribedDataSetTarget); + UA_TargetVariablesDataType_clear(&cfg->subscribedDataSet.target); } } @@ -420,46 +407,12 @@ UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, } } -UA_StatusCode -UA_FieldTargetVariable_copy(const UA_FieldTargetVariable *src, - UA_FieldTargetVariable *dst) { - memcpy(dst, src, sizeof(UA_FieldTargetVariable)); - return UA_FieldTargetDataType_copy(&src->targetVariable, - &dst->targetVariable); -} - -UA_StatusCode -UA_TargetVariables_copy(const UA_TargetVariables *src, UA_TargetVariables *dst) { - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memcpy(dst, src, sizeof(UA_TargetVariables)); - if(src->targetVariablesSize > 0) { - dst->targetVariables = (UA_FieldTargetVariable*) - UA_calloc(src->targetVariablesSize, sizeof(UA_FieldTargetVariable)); - if(!dst->targetVariables) - return UA_STATUSCODE_BADOUTOFMEMORY; - for(size_t i = 0; i < src->targetVariablesSize; i++) - retVal |= UA_FieldTargetVariable_copy(&src->targetVariables[i], - &dst->targetVariables[i]); - } - return retVal; -} - -void -UA_TargetVariables_clear(UA_TargetVariables *tvs) { - for(size_t i = 0; i < tvs->targetVariablesSize; i++) { - UA_FieldTargetDataType_clear(&tvs->targetVariables[i].targetVariable); - } - if(tvs->targetVariablesSize > 0) - UA_free(tvs->targetVariables); - memset(tvs, 0, sizeof(UA_TargetVariables)); -} - /* This Method is used to initially set the SubscribedDataSet to * TargetVariablesType and to create the list of target Variables of a * SubscribedDataSetType. */ UA_StatusCode DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr, - size_t tvsSize, const UA_FieldTargetVariable *tvs) { + size_t tvsSize, const UA_FieldTargetDataType *tvs) { UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); if(UA_PubSubState_isEnabled(dsr->head.state)) { @@ -469,29 +422,36 @@ DataSetReader_createTargetVariables(UA_PubSubManager *psm, UA_DataSetReader *dsr return UA_STATUSCODE_BADCONFIGURATIONERROR; } - if(dsr->config.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize > 0) - UA_TargetVariables_clear(&dsr->config.subscribedDataSet.subscribedDataSetTarget); + UA_TargetVariablesDataType newVars; + UA_TargetVariablesDataType tmp = {tvsSize, (UA_FieldTargetDataType*)(uintptr_t)tvs}; + UA_StatusCode res = UA_TargetVariablesDataType_copy(&tmp, &newVars); + if(res != UA_STATUSCODE_GOOD) + return res; - /* Set subscribed dataset to TargetVariableType */ - dsr->config.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - UA_TargetVariables tmp; - tmp.targetVariablesSize = tvsSize; - tmp.targetVariables = (UA_FieldTargetVariable*)(uintptr_t)tvs; - return UA_TargetVariables_copy(&tmp, &dsr->config.subscribedDataSet.subscribedDataSetTarget); + UA_TargetVariablesDataType_clear(&dsr->config.subscribedDataSet.target); + dsr->config.subscribedDataSet.target = newVars; + return UA_STATUSCODE_GOOD; } static void DataSetReader_processRaw(UA_PubSubManager *psm, UA_DataSetReader *dsr, UA_DataSetMessage* msg) { UA_LOG_TRACE_PUBSUB(psm->logging, dsr, "Received RAW Frame"); + + if(dsr->config.dataSetMetaData.fieldsSize != + dsr->config.subscribedDataSet.target.targetVariablesSize) { + UA_LOG_ERROR_PUBSUB(psm->logging, dsr, "Inconsistent number of fields configured"); + return; + } + msg->data.keyFrameData.fieldCount = (UA_UInt16) dsr->config.dataSetMetaData.fieldsSize; /* Start iteration from beginning of rawFields buffer */ size_t offset = 0; - for(size_t i = 0; i < dsr->config.dataSetMetaData.fieldsSize; i++) { - UA_FieldTargetVariable *tv = - &dsr->config.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; + UA_TargetVariablesDataType *tvs = &dsr->config.subscribedDataSet.target; + for(size_t i = 0; i < tvs->targetVariablesSize ; i++) { + UA_FieldTargetDataType *tv = &tvs->targetVariables[i]; /* TODO The datatype reference should be part of the internal * pubsub configuration to avoid the time-expensive lookup */ @@ -548,36 +508,22 @@ DataSetReader_processRaw(UA_PubSubManager *psm, UA_DataSetReader *dsr, } /* Write the value */ - if(tv->externalDataValue) { - if(tv->beforeWrite) - tv->beforeWrite(psm->sc.server, &dsr->head.identifier, - &dsr->linkedReaderGroup->head.identifier, - &tv->targetVariable.targetNodeId, - tv->targetVariableContext, tv->externalDataValue); - memcpy((*tv->externalDataValue)->value.data, value, type->memSize); - if(tv->afterWrite) - tv->afterWrite(psm->sc.server, &dsr->head.identifier, - &dsr->linkedReaderGroup->head.identifier, - &tv->targetVariable.targetNodeId, - tv->targetVariableContext, tv->externalDataValue); + UA_WriteValue writeVal; + UA_WriteValue_init(&writeVal); + writeVal.attributeId = tv->attributeId; + writeVal.indexRange = tv->receiverIndexRange; + writeVal.nodeId = tv->targetNodeId; + if(dsr->config.dataSetMetaData.fields[i].valueRank > 0) { + UA_Variant_setArray(&writeVal.value.value, value, elementCount, type); } else { - UA_WriteValue writeVal; - UA_WriteValue_init(&writeVal); - writeVal.attributeId = tv->targetVariable.attributeId; - writeVal.indexRange = tv->targetVariable.receiverIndexRange; - writeVal.nodeId = tv->targetVariable.targetNodeId; - if(dsr->config.dataSetMetaData.fields[i].valueRank > 0) { - UA_Variant_setArray(&writeVal.value.value, value, elementCount, type); - } else { - UA_Variant_setScalar(&writeVal.value.value, value, type); - } - writeVal.value.hasValue = true; - Operation_Write(psm->sc.server, &psm->sc.server->adminSession, NULL, &writeVal, &res); - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, - "Error writing KeyFrame field %u: %s", - (unsigned)i, UA_StatusCode_name(res)); - } + UA_Variant_setScalar(&writeVal.value.value, value, type); + } + writeVal.value.hasValue = true; + Operation_Write(psm->sc.server, &psm->sc.server->adminSession, NULL, &writeVal, &res); + if(res != UA_STATUSCODE_GOOD) { + UA_LOG_WARNING_PUBSUB(psm->logging, dsr, + "Error writing KeyFrame field %u: %s", + (unsigned)i, UA_StatusCode_name(res)); } /* Clean up if string-type (with mallocs) was used */ @@ -688,8 +634,7 @@ UA_DataSetReader_process(UA_PubSubManager *psm, UA_DataSetReader *dsr, return; } - UA_TargetVariables *tvs = - &dsr->config.subscribedDataSet.subscribedDataSetTarget; + UA_TargetVariablesDataType *tvs = &dsr->config.subscribedDataSet.target;; if(tvs->targetVariablesSize != fieldCount) { UA_LOG_WARNING_PUBSUB(psm->logging, dsr, "Number of fields does not match the " @@ -700,39 +645,17 @@ UA_DataSetReader_process(UA_PubSubManager *psm, UA_DataSetReader *dsr, /* Write the message fields. RT has the external data value configured. */ UA_StatusCode res = UA_STATUSCODE_GOOD; for(size_t i = 0; i < fieldCount; i++) { - UA_FieldTargetVariable *tv = &tvs->targetVariables[i]; + UA_FieldTargetDataType *tv = &tvs->targetVariables[i]; UA_DataValue *field = &msg->data.keyFrameData.dataSetFields[i]; if(!field->hasValue) continue; - /* RT-path: write directly into the target memory */ - if(tv->externalDataValue) { - if(field->value.type != (*tv->externalDataValue)->value.type) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsr, "Mismatching type"); - continue; - } - - if(tv->beforeWrite) - tv->beforeWrite(psm->sc.server, &dsr->head.identifier, - &dsr->linkedReaderGroup->head.identifier, - &tv->targetVariable.targetNodeId, - tv->targetVariableContext, tv->externalDataValue); - memcpy((*tv->externalDataValue)->value.data, - field->value.data, field->value.type->memSize); - if(tv->afterWrite) - tv->afterWrite(psm->sc.server, &dsr->head.identifier, - &dsr->linkedReaderGroup->head.identifier, - &tv->targetVariable.targetNodeId, - tv->targetVariableContext, tv->externalDataValue); - continue; - } - /* Write via the Write-Service */ UA_WriteValue writeVal; UA_WriteValue_init(&writeVal); - writeVal.attributeId = tv->targetVariable.attributeId; - writeVal.indexRange = tv->targetVariable.receiverIndexRange; - writeVal.nodeId = tv->targetVariable.targetNodeId; + writeVal.attributeId = tv->attributeId; + writeVal.indexRange = tv->receiverIndexRange; + writeVal.nodeId = tv->targetNodeId; writeVal.value = *field; Operation_Write(psm->sc.server, &psm->sc.server->adminSession, NULL, &writeVal, &res); @@ -840,14 +763,15 @@ UA_Server_disableDataSetReader(UA_Server *server, const UA_NodeId dsrId) { UA_StatusCode UA_Server_setDataSetReaderTargetVariables(UA_Server *server, const UA_NodeId dsrId, - size_t tvsSize, const UA_FieldTargetVariable *tvs) { + size_t targetVariablesSize, const UA_FieldTargetDataType *targetVariables) { if(!server) return UA_STATUSCODE_BADINVALIDARGUMENT; UA_LOCK(&server->serviceMutex); UA_PubSubManager *psm = getPSM(server); UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dsrId); UA_StatusCode res = (dsr) ? - DataSetReader_createTargetVariables(psm, dsr, tvsSize, tvs) : UA_STATUSCODE_BADNOTFOUND; + DataSetReader_createTargetVariables(psm, dsr, targetVariablesSize, + targetVariables) : UA_STATUSCODE_BADNOTFOUND; UA_UNLOCK(&server->serviceMutex); return res; } diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index 9da7eb33d..d476f8531 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -990,7 +990,7 @@ UA_PubSubDataSetReader_generateKeyFrameMessage(UA_Server *server, UA_DataSetMessage *dsm, UA_DataSetReader *dsr) { /* Prepare DataSetMessageContent */ - UA_TargetVariables *tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget; + UA_TargetVariablesDataType *tv = &dsr->config.subscribedDataSet.target; dsm->header.dataSetMessageValid = true; dsm->header.dataSetMessageType = UA_DATASETMESSAGE_DATAKEYFRAME; dsm->data.keyFrameData.fieldCount = (UA_UInt16) tv->targetVariablesSize; @@ -1003,16 +1003,15 @@ UA_PubSubDataSetReader_generateKeyFrameMessage(UA_Server *server, &dsr->config.dataSetMetaData; for(size_t counter = 0; counter < tv->targetVariablesSize; counter++) { - /* Sample the value and set the source in the reader config */ + /* Read the value and set the source in the reader config */ UA_DataValue *dfv = &dsm->data.keyFrameData.dataSetFields[counter]; - UA_FieldTargetVariable *ftv = &tv->targetVariables[counter]; + UA_FieldTargetDataType *ftv = &tv->targetVariables[counter]; UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); - rvi.nodeId = ftv->targetVariable.targetNodeId; - rvi.attributeId = ftv->targetVariable.attributeId; - rvi.indexRange = ftv->targetVariable.writeIndexRange; - + rvi.nodeId = ftv->targetNodeId; + rvi.attributeId = ftv->attributeId; + rvi.indexRange = ftv->writeIndexRange; *dfv = readWithSession(server, &server->adminSession, &rvi, UA_TIMESTAMPSTORETURN_NEITHER); @@ -1278,20 +1277,17 @@ UA_Server_computeReaderGroupOffsetTable(UA_Server *server, UA_NodeId_copy(&dsr->head.identifier, &o->component); break; case UA_PUBSUBOFFSETTYPE_DATASETFIELD_DATAVALUE: - tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. - targetVariables[fieldindex].targetVariable; + tv = &dsr->config.subscribedDataSet.target.targetVariables[fieldindex]; UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; break; case UA_PUBSUBOFFSETTYPE_DATASETFIELD_VARIANT: - tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. - targetVariables[fieldindex].targetVariable; + tv = &dsr->config.subscribedDataSet.target.targetVariables[fieldindex]; UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; break; case UA_PUBSUBOFFSETTYPE_DATASETFIELD_RAW: - tv = &dsr->config.subscribedDataSet.subscribedDataSetTarget. - targetVariables[fieldindex].targetVariable; + tv = &dsr->config.subscribedDataSet.target.targetVariables[fieldindex]; UA_NodeId_copy(&tv->targetNodeId, &o->component); fieldindex++; break; diff --git a/tests/pubsub/check_pubsub_config_freeze.c b/tests/pubsub/check_pubsub_config_freeze.c index a7ab43385..37569d080 100644 --- a/tests/pubsub/check_pubsub_config_freeze.c +++ b/tests/pubsub/check_pubsub_config_freeze.c @@ -349,8 +349,8 @@ START_TEST(CreateConfigWithStaticFieldSource) { memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - fieldConfig.field.variable.rtValueSource.staticValueSource = &dataValue; + fieldConfig.field.variable.publishParameters.publishedVariable = + UA_NS0ID(SERVER_SERVERSTATUS); UA_NodeId localDataSetField; retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result; diff --git a/tests/pubsub/check_pubsub_custom_state_machine.c b/tests/pubsub/check_pubsub_custom_state_machine.c index 42e1d87ec..62aacc17e 100644 --- a/tests/pubsub/check_pubsub_custom_state_machine.c +++ b/tests/pubsub/check_pubsub_custom_state_machine.c @@ -161,8 +161,6 @@ START_TEST(CustomPublisher) { for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { /* TODO: Point to a variable in the information model */ memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); } @@ -387,10 +385,6 @@ connectionStateMachine(UA_Server *server, const UA_NodeId componentId, return UA_STATUSCODE_GOOD; } -/* Simulate a custom data sink (e.g. shared memory) */ -UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; - /* If the external data source is written over the information model, the * externalDataWriteCallback will be triggered. The user has to take care and assure * that the write leads not to synchronization issues and race conditions. */ @@ -521,12 +515,9 @@ addSubscribedVariables (UA_Server *server) { /* Set the subscribed data to TargetVariable type */ readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; /* Create the TargetVariables with respect to DataSetMetaData fields */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = - readerConfig.dataSetMetaData.fieldsSize; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = - (UA_FieldTargetVariable *)UA_calloc( - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, - sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariablesSize = readerConfig.dataSetMetaData.fieldsSize; + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -545,33 +536,10 @@ addSubscribedVariables (UA_Server *server) { UA_QUALIFIEDNAME(1, "Subscribed UInt32"), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - repeatedFieldValues[i] = 0; - repeatedDataValueRT[i] = UA_DataValue_new(); - UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], - &UA_TYPES[UA_TYPES_UINT32]); - repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; - repeatedDataValueRT[i]->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[i]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); - - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(ftdt); - ftdt->attributeId = UA_ATTRIBUTEID_VALUE; - ftdt->targetNodeId = newnodeId; - /* set both before and after write callback to show the usage */ - tv->beforeWrite = subscribeBeforeWriteCallback; - tv->externalDataValue = &repeatedDataValueRT[i]; - tv->afterWrite = subscribeAfterWriteCallback; + UA_FieldTargetDataType *tv = &readerConfig.subscribedDataSet.target.targetVariables[i]; + tv->attributeId = UA_ATTRIBUTEID_VALUE; + tv->targetNodeId = newnodeId; } } @@ -610,14 +578,7 @@ addDataSetReader(UA_Server *server) { addSubscribedVariables(server); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - UA_FieldTargetDataType_clear(ftdt); - } - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.subscribedDataSet.target.targetVariables); UA_free(readerConfig.dataSetMetaData.fields); UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); } @@ -637,11 +598,7 @@ START_TEST(CustomSubscriber) { pthread_join(listenThread, NULL); - UA_StatusCode rv = UA_Server_delete(server); - ck_assert_int_eq(rv, UA_STATUSCODE_GOOD); - for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - UA_DataValue_delete(repeatedDataValueRT[i]); - } + UA_Server_delete(server); } END_TEST int main(void) { diff --git a/tests/pubsub/check_pubsub_get_state.c b/tests/pubsub/check_pubsub_get_state.c index 141cdab7b..840ee0b26 100644 --- a/tests/pubsub/check_pubsub_get_state.c +++ b/tests/pubsub/check_pubsub_get_state.c @@ -219,27 +219,21 @@ static void AddDataSetReader( UA_Variant_setScalar(&attr.value, &SubscriberData, &UA_TYPES[UA_TYPES_INT32]); ck_assert(UA_Server_addVariableNode(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, opSubscriberVarId) == UA_STATUSCODE_GOOD); + UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), + UA_QUALIFIEDNAME(1, "Subscribed Int32"), + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), + attr, NULL, opSubscriberVarId) == UA_STATUSCODE_GOOD); - UA_FieldTargetVariable *pTargetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); - assert(pTargetVariables != 0); - - UA_FieldTargetDataType_init(&pTargetVariables[0].targetVariable); - - pTargetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - pTargetVariables[0].targetVariable.targetNodeId = *opSubscriberVarId; + UA_FieldTargetDataType targetVariable; + UA_FieldTargetDataType_init(&targetVariable); + targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + targetVariable.targetNodeId = *opSubscriberVarId; ck_assert(UA_Server_DataSetReader_createTargetVariables(server, *opDataSetReaderId, - readerConfig.dataSetMetaData.fieldsSize, pTargetVariables) == UA_STATUSCODE_GOOD); - - UA_FieldTargetDataType_clear(&pTargetVariables[0].targetVariable); - UA_free(pTargetVariables); - pTargetVariables = 0; + 1, &targetVariable) == UA_STATUSCODE_GOOD); UA_free(pDataSetMetaData->fields); - pDataSetMetaData->fields = 0; + pDataSetMetaData->fields = NULL; } /***************************************************************************************************/ diff --git a/tests/pubsub/check_pubsub_mqtt.c b/tests/pubsub/check_pubsub_mqtt.c index e159e91ef..8acb7d25e 100644 --- a/tests/pubsub/check_pubsub_mqtt.c +++ b/tests/pubsub/check_pubsub_mqtt.c @@ -282,8 +282,8 @@ START_TEST(SinglePublishSubscribeDateTime){ ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); /* Create the TargetVariables with respect to DataSetMetaData fields */ - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -301,20 +301,14 @@ START_TEST(SinglePublishSubscribeDateTime){ UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } retval = UA_Server_DataSetReader_createTargetVariables(server, subscribedDataSetIdent, readerConfig.dataSetMetaData.fieldsSize, targetVars); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); - UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/tests/pubsub/check_pubsub_offset.c b/tests/pubsub/check_pubsub_offset.c index f2cbc465b..67a3f799a 100644 --- a/tests/pubsub/check_pubsub_offset.c +++ b/tests/pubsub/check_pubsub_offset.c @@ -18,21 +18,10 @@ #define PUBSUB_CONFIG_FIELD_COUNT 10 UA_Server *server; - +UA_DataSetReaderConfig readerConfig; static UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; static UA_NodeId readerGroupIdentifier, readerIdentifier; -/* Values in static locations. We cycle the dvPointers double-pointer to the - * next with atomic operations. */ -UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT]; - -UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT]; -UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT]; - -UA_DataSetReaderConfig readerConfig; - static UA_NetworkAddressUrlDataType networkAddressUrl = {{0, NULL}, UA_STRING_STATIC("opc.udp://224.0.0.22:4840/")}; static UA_String transportProfile = @@ -50,14 +39,6 @@ static void teardown(void) { } START_TEST(PublisherOffsets) { - /* Prepare the values */ - for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - valueStore[i] = (UA_UInt32) i + 1; - UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]); - dvStore[i].hasValue = true; - dvPointers[i] = &dvStore[i]; - } - /* Add a PubSubConnection */ UA_PubSubConnectionConfig connectionConfig; memset(&connectionConfig, 0, sizeof(connectionConfig)); @@ -84,8 +65,6 @@ START_TEST(PublisherOffsets) { for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { /* TODO: Point to a variable in the information model */ memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true; - dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i]; UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); } @@ -230,12 +209,10 @@ addSubscribedVariables (UA_Server *server) { /* Set the subscribed data to TargetVariable type */ readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; /* Create the TargetVariables with respect to DataSetMetaData fields */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = - readerConfig.dataSetMetaData.fieldsSize; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = - (UA_FieldTargetVariable *)UA_calloc( - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, - sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariablesSize = readerConfig.dataSetMetaData.fieldsSize; + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize, sizeof(UA_FieldTargetDataType)); + for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -254,28 +231,10 @@ addSubscribedVariables (UA_Server *server) { UA_QUALIFIEDNAME(1, "Subscribed UInt32"), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); - repeatedFieldValues[i] = 0; - repeatedDataValueRT[i] = UA_DataValue_new(); - UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i], - &UA_TYPES[UA_TYPES_UINT32]); - repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE; - repeatedDataValueRT[i]->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - memset(&valueBackend, 0, sizeof(UA_ValueBackend)); - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[i]; - UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend); - - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(ftdt); - ftdt->attributeId = UA_ATTRIBUTEID_VALUE; - ftdt->targetNodeId = newnodeId; + UA_FieldTargetDataType *tv = &readerConfig.subscribedDataSet.target.targetVariables[i]; + tv->attributeId = UA_ATTRIBUTEID_VALUE; + tv->targetNodeId = newnodeId; } } @@ -313,14 +272,7 @@ addDataSetReader(UA_Server *server) { addSubscribedVariables(server); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); - for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { - UA_FieldTargetVariable *tv = - &readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i]; - UA_FieldTargetDataType *ftdt = &tv->targetVariable; - UA_FieldTargetDataType_clear(ftdt); - } - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.subscribedDataSet.target.targetVariables); UA_free(readerConfig.dataSetMetaData.fields); UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); } @@ -344,9 +296,6 @@ START_TEST(SubscriberOffsets) { } UA_PubSubOffsetTable_clear(&ot); - for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) { - UA_DataValue_delete(repeatedDataValueRT[i]); - } } END_TEST int main(void) { diff --git a/tests/pubsub/check_pubsub_publisherid.c b/tests/pubsub/check_pubsub_publisherid.c index 4d91a53d2..2fe2c6a04 100644 --- a/tests/pubsub/check_pubsub_publisherid.c +++ b/tests/pubsub/check_pubsub_publisherid.c @@ -117,7 +117,6 @@ AddPublishedDataSet(UA_NodeId *pWriterGroupId, char *pPublishedDataSetName, dataSetFieldConfig.field.variable.publishParameters.publishedVariable = *opPublishedVarId; dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; if (UseFastPath) { - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; *oppFastPathPublisherDataValue = UA_DataValue_new(); ck_assert(*oppFastPathPublisherDataValue != 0); UA_Int32 *pPublisherData = UA_Int32_new(); @@ -237,25 +236,17 @@ AddDataSetReader(UA_NodeId *pReaderGroupId, char *pName, ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_setVariableNode_valueBackend(server, *opSubscriberVarId, valueBackend)); } - UA_FieldTargetVariable *pTargetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); - ck_assert(pTargetVariables != 0); - - UA_FieldTargetDataType_init(&pTargetVariables[0].targetVariable); - - pTargetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - pTargetVariables[0].targetVariable.targetNodeId = *opSubscriberVarId; + UA_FieldTargetDataType targetVariable; + UA_FieldTargetDataType_init(&targetVariable); + targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; + targetVariable.targetNodeId = *opSubscriberVarId; ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_DataSetReader_createTargetVariables(server, *opDataSetReaderId, - readerConfig.dataSetMetaData.fieldsSize, pTargetVariables)); - - UA_FieldTargetDataType_clear(&pTargetVariables[0].targetVariable); - UA_free(pTargetVariables); - pTargetVariables = 0; + 1, &targetVariable)); UA_free(pDataSetMetaData->fields); - pDataSetMetaData->fields = 0; + pDataSetMetaData->fields = NULL; } static void diff --git a/tests/pubsub/check_pubsub_sks_client.c b/tests/pubsub/check_pubsub_sks_client.c index 54b0a923e..bd4cfbbfd 100644 --- a/tests/pubsub/check_pubsub_sks_client.c +++ b/tests/pubsub/check_pubsub_sks_client.c @@ -430,8 +430,8 @@ addSubscriber(UA_Server *server) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); ck_assert_int_eq(retval, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)UA_calloc( - readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType*) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_LocalizedText_copy(&readerConfig.dataSetMetaData.fields->description, @@ -447,14 +447,12 @@ addSubscriber(UA_Server *server) { UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields->name.data), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - /* For creating Targetvariables */ - UA_FieldTargetDataType_init(&targetVars->targetVariable); - targetVars->targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars->targetVariable.targetNodeId = newNode; + targetVars->attributeId = UA_ATTRIBUTEID_VALUE; + targetVars->targetNodeId = newNode; - retval = UA_Server_DataSetReader_createTargetVariables( - server, readerIdentifier, readerConfig.dataSetMetaData.fieldsSize, targetVars); - UA_FieldTargetDataType_clear(&targetVars->targetVariable); + retval = UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, + readerConfig.dataSetMetaData.fieldsSize, + targetVars); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); diff --git a/tests/pubsub/check_pubsub_subscribe.c b/tests/pubsub/check_pubsub_subscribe.c index af5578263..30d8e81e2 100644 --- a/tests/pubsub/check_pubsub_subscribe.c +++ b/tests/pubsub/check_pubsub_subscribe.c @@ -606,17 +606,16 @@ START_TEST(SinglePublishSubscribeDateTime) { pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; pMetaData->fields[0].valueRank = -1; /* scalar */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariablesSize = 1; + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType *) + UA_calloc(1, sizeof(UA_FieldTargetDataType)); /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = nodeIdDateTime; + readerConfig.subscribedDataSet.target.targetVariables->attributeId = UA_ATTRIBUTEID_VALUE; + readerConfig.subscribedDataSet.target.targetVariables->targetNodeId = nodeIdDateTime; retVal |= UA_Server_addDataSetReader(server, readerGroupId, &readerConfig, &readerIdentifier); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.subscribedDataSet.target.targetVariables); /* run server - publisher and subscriber */ ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); @@ -692,19 +691,18 @@ START_TEST(SinglePublishSubscribeDateTimeRaw) { pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; pMetaData->fields[0].valueRank = -1; /* scalar */ - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariablesSize = 1; + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType*) + UA_calloc(1, sizeof(UA_FieldTargetDataType)); /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = nodeIdDateTime; + readerConfig.subscribedDataSet.target.targetVariables->attributeId = UA_ATTRIBUTEID_VALUE; + readerConfig.subscribedDataSet.target.targetVariables->targetNodeId = nodeIdDateTime; retVal |= UA_Server_addDataSetReader(server, readerGroupId, &readerConfig, &readerIdentifier); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); /* run server - publisher and subscriber */ + UA_free(readerConfig.subscribedDataSet.target.targetVariables); ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); UA_free(pMetaData->fields); }END_TEST @@ -831,19 +829,16 @@ START_TEST(SinglePublishSubscribeInt32) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); - /* run server - publisher and subscriber */ ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); @@ -979,16 +974,14 @@ START_TEST(SinglePublishSubscribeInt32StatusCode) { UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* Write the value back - but with a status code */ @@ -1149,16 +1142,14 @@ START_TEST(SinglePublishSubscribeInt64) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -1297,16 +1288,14 @@ START_TEST(SinglePublishSubscribeBool) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -1448,16 +1437,14 @@ START_TEST(SinglePublishSubscribewithValidIdentifiers) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -1533,12 +1520,6 @@ START_TEST(SinglePublishSubscribeHeartbeat) { retVal |= UA_Server_addDataSetReader(server, readerGroupId, &readerConfig, &readerIdentifier); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - //UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); @@ -1676,16 +1657,14 @@ START_TEST(SinglePublishSubscribeWithoutPayloadHeader) { UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -1826,10 +1805,10 @@ START_TEST(MultiPublishSubscribeInt32) { ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); /* For creating Targetvariable */ - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); @@ -1849,11 +1828,11 @@ START_TEST(MultiPublishSubscribeInt32) { ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); /* Create Targetvariable */ - targetVar.targetVariable.targetNodeId = newnodeId2; + targetVar.targetNodeId = newnodeId2; retVal |= UA_Server_DataSetReader_createTargetVariables(server, reader2Id, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); + UA_FieldTargetDataType_clear(&targetVar); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -2150,16 +2129,15 @@ START_TEST(SinglePublishOnDemand) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); + UA_FieldTargetDataType_clear(&targetVar); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -2307,16 +2285,14 @@ START_TEST(ValidConfiguredSizPublishSubscribe) { UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ @@ -2453,16 +2429,14 @@ START_TEST(InvalidConfiguredSizPublishSubscribe) { UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; + + UA_FieldTargetDataType targetVar; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); /* run server - publisher and subscriber */ diff --git a/tests/pubsub/check_pubsub_subscribe_config_freeze.c b/tests/pubsub/check_pubsub_subscribe_config_freeze.c index 96a8e8e30..a37a56634 100644 --- a/tests/pubsub/check_pubsub_subscribe_config_freeze.c +++ b/tests/pubsub/check_pubsub_subscribe_config_freeze.c @@ -201,8 +201,8 @@ START_TEST(CreateLockAndEditConfiguration) { folderBrowseName, UA_NODEID_NUMERIC (0, UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *) - UA_calloc(dataSetReaderConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) + UA_calloc(dataSetReaderConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < dataSetReaderConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; @@ -219,9 +219,8 @@ START_TEST(CreateLockAndEditConfiguration) { UA_QUALIFIEDNAME(1, (char *)dataSetReaderConfig.dataSetMetaData.fields[i].name.data), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); - UA_FieldTargetDataType_init(&targetVars[i].targetVariable); - targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetVariable.targetNodeId = newNode; + targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; + targetVars[i].targetNodeId = newNode; } retVal = UA_Server_enableDataSetReader(server, dataSetReader1); @@ -251,9 +250,6 @@ START_TEST(CreateLockAndEditConfiguration) { targetVars); ck_assert(retVal == UA_STATUSCODE_GOOD); retVal = UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader2); - for(size_t i = 0; i < dataSetReaderConfig.dataSetMetaData.fieldsSize; i++) - UA_FieldTargetDataType_clear(&targetVars[i].targetVariable); - UA_free(targetVars); UA_free(dataSetReaderConfig.dataSetMetaData.fields); ck_assert(retVal == UA_STATUSCODE_GOOD); diff --git a/tests/pubsub/check_pubsub_subscribe_encrypted.c b/tests/pubsub/check_pubsub_subscribe_encrypted.c index ef6d972ee..e23cc6864 100644 --- a/tests/pubsub/check_pubsub_subscribe_encrypted.c +++ b/tests/pubsub/check_pubsub_subscribe_encrypted.c @@ -185,18 +185,16 @@ START_TEST(SinglePublishSubscribeDateTime) { UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 1; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize, sizeof(UA_FieldTargetVariable)); + readerConfig.subscribedDataSet.target.targetVariablesSize = 1; + readerConfig.subscribedDataSet.target.targetVariables = (UA_FieldTargetDataType *) + UA_calloc(readerConfig.subscribedDataSet.target.targetVariablesSize, sizeof(UA_FieldTargetDataType)); /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable); - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[0].targetVariable.targetNodeId = newnodeId; - retVal |= UA_Server_addDataSetReader(server, readerGroupTest, &readerConfig, - &readerIdentifier); + readerConfig.subscribedDataSet.target.targetVariables[0].attributeId = UA_ATTRIBUTEID_VALUE; + readerConfig.subscribedDataSet.target.targetVariables[0].targetNodeId = newnodeId; + retVal |= UA_Server_addDataSetReader(server, readerGroupTest, &readerConfig, &readerIdentifier); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); + UA_free(readerConfig.subscribedDataSet.target.targetVariables); retVal = UA_Server_enableAllPubSubComponents(server); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); @@ -375,16 +373,13 @@ START_TEST(SinglePublishSubscribeInt32) { UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newnodeId); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); - /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = newnodeId; - retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, - 1, &targetVar); + + UA_FieldTargetDataType targetVar; + memset(&targetVar, 0, sizeof(UA_FieldTargetDataType)); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = newnodeId; + retVal |= UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); retVal = UA_Server_enableAllPubSubComponents(server); diff --git a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c index daffcdf7f..fe34a6e33 100644 --- a/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c +++ b/tests/pubsub/check_pubsub_subscribe_msgrcvtimeout.c @@ -139,7 +139,6 @@ AddPublishedDataSet(UA_NodeId *pWriterGroupId, char *pPublishedDataSetName, dataSetFieldConfig.field.variable.publishParameters.publishedVariable = *opPublishedVarId; dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; if (UseFastPath) { - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; pFastPathPublisherValue = UA_DataValue_new(); ck_assert(pFastPathPublisherValue != 0); UA_Int32 *pPublisherData = UA_Int32_new(); @@ -260,19 +259,16 @@ AddDataSetReader(UA_NodeId *pReaderGroupId, char *pName, UA_UInt32 PublisherId, valueBackend)); } - UA_FieldTargetVariable *pTargetVariables = (UA_FieldTargetVariable *) - UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType *pTargetVariables = (UA_FieldTargetDataType *) + UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); ck_assert(pTargetVariables != 0); - UA_FieldTargetDataType_init(&pTargetVariables[0].targetVariable); - - pTargetVariables[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - pTargetVariables[0].targetVariable.targetNodeId = *opSubscriberVarId; + pTargetVariables->attributeId = UA_ATTRIBUTEID_VALUE; + pTargetVariables->targetNodeId = *opSubscriberVarId; ck_assert(UA_Server_DataSetReader_createTargetVariables(server, *opDataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, pTargetVariables) == UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&pTargetVariables[0].targetVariable); UA_free(pTargetVariables); pTargetVariables = 0; diff --git a/tests/pubsub/check_pubsub_udp_unicast.c b/tests/pubsub/check_pubsub_udp_unicast.c index e6e4109c1..302abf1a7 100644 --- a/tests/pubsub/check_pubsub_udp_unicast.c +++ b/tests/pubsub/check_pubsub_udp_unicast.c @@ -256,16 +256,14 @@ setupSubscribing(UA_Server *server, UA_NodeId connectionId, &readerIdentifier); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetVariable targetVar; - memset(&targetVar, 0, sizeof(UA_FieldTargetVariable)); + UA_FieldTargetDataType targetVar; /* For creating Targetvariable */ - UA_FieldTargetDataType_init(&targetVar.targetVariable); - targetVar.targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVar.targetVariable.targetNodeId = targetNodeId; + UA_FieldTargetDataType_init(&targetVar); + targetVar.attributeId = UA_ATTRIBUTEID_VALUE; + targetVar.targetNodeId = targetNodeId; retVal = UA_Server_DataSetReader_createTargetVariables(server, readerIdentifier, - 1, &targetVar); + 1, &targetVar); ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_FieldTargetDataType_clear(&targetVar.targetVariable); UA_free(pMetaData->fields); } From 62e78f84248b8057a5f210a3df397ad955644767 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 15 Jan 2025 22:32:08 +0100 Subject: [PATCH 146/158] refactor(pubsub): Remove the old custom lifecycle callback option --- include/open62541/server_pubsub.h | 24 +-------------- src/pubsub/ua_pubsub_writergroup.c | 47 ++++++------------------------ 2 files changed, 10 insertions(+), 61 deletions(-) diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index 1f96db6be..d88441f2f 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -433,27 +433,6 @@ typedef enum { UA_PUBSUB_ENCODING_JSON } UA_PubSubEncodingType; -/** - * The user can define his own callback implementation for publishing and - * subscribing. The user must take care of the callback to call for every - * publishing or subscibing interval. The configured base time and timer policy - * are provided as an argument so that the user can implement his callback - * (thread) considering base time and timer policies */ - -typedef struct { - UA_StatusCode (*addCustomCallback)(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, void *data, - UA_Double interval_ms, UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId); - UA_StatusCode (*changeCustomCallback)(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, - UA_TimerPolicy timerPolicy); - void (*removeCustomCallback)(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId); -} UA_PubSub_CallbackLifecycle; - typedef struct { UA_String name; UA_UInt16 writerGroupId; @@ -464,8 +443,7 @@ typedef struct { UA_ExtensionObject messageSettings; UA_KeyValueMap groupProperties; UA_PubSubEncodingType encodingMimeType; - /* PubSub Manager Callback */ - UA_PubSub_CallbackLifecycle pubsubManagerCallback; + /* non std. config parameter. maximum count of embedded DataSetMessage in * one NetworkMessage */ UA_UInt16 maxEncapsulatedDataSetMessageCount; diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 83bee12fd..ce9d75cc8 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -56,17 +56,6 @@ UA_WriterGroup_canConnect(UA_WriterGroup *wg) { return true; } -static void -UA_WriterGroup_publishCallback_server(UA_Server *server, UA_WriterGroup *wg) { - UA_PubSubManager *psm = getPSM(server); - if(!psm) { - UA_LOG_WARNING_PUBSUB(server->config.logging, wg, - "Cannot publish -- PubSub not configured for the server"); - return; - } - UA_WriterGroup_publishCallback(psm, wg); -} - UA_StatusCode UA_WriterGroup_addPublishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); @@ -75,39 +64,21 @@ UA_WriterGroup_addPublishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { if(wg->publishCallbackId != 0) return UA_STATUSCODE_GOOD; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - if(wg->config.pubsubManagerCallback.addCustomCallback) { - /* Use configured mechanism for cyclic callbacks */ - retval = wg->config.pubsubManagerCallback. - addCustomCallback(psm->sc.server, wg->head.identifier, - (UA_ServerCallback)UA_WriterGroup_publishCallback_server, - wg, wg->config.publishingInterval, - NULL, UA_TIMERPOLICY_CURRENTTIME, - &wg->publishCallbackId); - } else { - /* Use EventLoop for cyclic callbacks */ - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); - retval = el->addTimer(el, (UA_Callback)UA_WriterGroup_publishCallback, - psm, wg, wg->config.publishingInterval, - NULL /* TODO: use basetime */, - UA_TIMERPOLICY_CURRENTTIME, - &wg->publishCallbackId); - } - - return retval; + /* Use EventLoop for cyclic callbacks */ + UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + return el->addTimer(el, (UA_Callback)UA_WriterGroup_publishCallback, + psm, wg, wg->config.publishingInterval, + NULL /* TODO: use basetime */, + UA_TIMERPOLICY_CURRENTTIME, + &wg->publishCallbackId); } void UA_WriterGroup_removePublishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { if(wg->publishCallbackId == 0) return; - if(wg->config.pubsubManagerCallback.removeCustomCallback) { - wg->config.pubsubManagerCallback. - removeCustomCallback(psm->sc.server, wg->head.identifier, wg->publishCallbackId); - } else { - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); - el->removeTimer(el, wg->publishCallbackId); - } + UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + el->removeTimer(el, wg->publishCallbackId); wg->publishCallbackId = 0; } From 33380c86461f843f24e161f673ee6ede5450c576 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Wed, 15 Jan 2025 22:44:56 +0100 Subject: [PATCH 147/158] refactor(pubsub): Remove option for different EventLoop for each PubSubConnection --- include/open62541/server_pubsub.h | 5 ----- src/pubsub/ua_pubsub_connection.c | 11 ++--------- src/pubsub/ua_pubsub_internal.h | 5 ----- src/pubsub/ua_pubsub_reader.c | 5 ++--- src/pubsub/ua_pubsub_readergroup.c | 2 +- src/pubsub/ua_pubsub_writer.c | 5 ++--- src/pubsub/ua_pubsub_writergroup.c | 8 ++++---- 7 files changed, 11 insertions(+), 30 deletions(-) diff --git a/include/open62541/server_pubsub.h b/include/open62541/server_pubsub.h index d88441f2f..4f7242717 100644 --- a/include/open62541/server_pubsub.h +++ b/include/open62541/server_pubsub.h @@ -232,11 +232,6 @@ typedef struct { UA_Variant connectionTransportSettings; UA_PUBSUB_COMPONENT_CONTEXT /* Context Configuration */ - - UA_EventLoop *eventLoop; /* Use an external EventLoop (use the EventLoop of - * the server if this is NULL). Propagates to the - * ReaderGroup/WriterGroup attached to the - * Connection. */ } UA_PubSubConnectionConfig; /* Add a new PubSub connection to the given server and open it. diff --git a/src/pubsub/ua_pubsub_connection.c b/src/pubsub/ua_pubsub_connection.c index ef97751f8..dcb047903 100644 --- a/src/pubsub/ua_pubsub_connection.c +++ b/src/pubsub/ua_pubsub_connection.c @@ -250,7 +250,7 @@ UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c) { /* The WriterGroups / ReaderGroups are not deleted. Try again in the next * iteration of the event loop.*/ if(!LIST_EMPTY(&c->writerGroups) || !LIST_EMPTY(&c->readerGroups)) { - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c); + UA_EventLoop *el = psm->sc.server->config.eventLoop; c->dc.callback = delayedPubSubConnection_delete; c->dc.application = psm; c->dc.context = c; @@ -455,13 +455,6 @@ disablePubSubConnection(UA_PubSubManager *psm, const UA_NodeId connectionId) { : UA_STATUSCODE_BADNOTFOUND; } -UA_EventLoop * -UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c) { - if(c->config.eventLoop) - return c->config.eventLoop; - return psm->sc.server->config.eventLoop; -} - /***********************/ /* Connection Handling */ /***********************/ @@ -820,7 +813,7 @@ UA_PubSubConnection_connect(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_Server *server = psm->sc.server; UA_LOCK_ASSERT(&server->serviceMutex); - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, c); + UA_EventLoop *el = psm->sc.server->config.eventLoop; if(!el) { UA_LOG_ERROR_PUBSUB(psm->logging, c, "No EventLoop configured"); return UA_STATUSCODE_BADINTERNALERROR;; diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index 0bd32b4a9..950e902a0 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -284,11 +284,6 @@ UA_PubSubConnectionConfig_clear(UA_PubSubConnectionConfig *connectionConfig); void UA_PubSubConnection_delete(UA_PubSubManager *psm, UA_PubSubConnection *c); -/* Returns either the eventloop configured in the connection or, in its absence, - * for the server */ -UA_EventLoop * -UA_PubSubConnection_getEL(UA_PubSubManager *psm, UA_PubSubConnection *c); - UA_StatusCode UA_PubSubConnection_setPubSubState(UA_PubSubManager *psm, UA_PubSubConnection *c, UA_PubSubState targetState); diff --git a/src/pubsub/ua_pubsub_reader.c b/src/pubsub/ua_pubsub_reader.c index 18b9a0509..4f3a0f7ac 100644 --- a/src/pubsub/ua_pubsub_reader.c +++ b/src/pubsub/ua_pubsub_reader.c @@ -385,7 +385,7 @@ UA_DataSetReader_setPubSubState(UA_PubSubManager *psm, UA_DataSetReader *dsr, /* Only keep the timeout callback if the reader is operational */ if(dsr->head.state != UA_PUBSUBSTATE_OPERATIONAL && dsr->msgRcvTimeoutTimerId != 0) { - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, rg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; el->removeTimer(el, dsr->msgRcvTimeoutTimerId); dsr->msgRcvTimeoutTimerId = 0; } @@ -600,8 +600,7 @@ UA_DataSetReader_process(UA_PubSubManager *psm, UA_DataSetReader *dsr, /* Configure / Update the timeout callback */ if(dsr->config.messageReceiveTimeout > 0.0) { - UA_EventLoop *el = - UA_PubSubConnection_getEL(psm, dsr->linkedReaderGroup->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; if(dsr->msgRcvTimeoutTimerId == 0) { el->addTimer(el, (UA_Callback)UA_DataSetReader_handleMessageReceiveTimeout, psm, dsr, dsr->config.messageReceiveTimeout, NULL, diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index d476f8531..b6a9637d9 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -816,7 +816,7 @@ UA_ReaderGroup_connect(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_Boolean val if(rg->config.transportSettings.encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) return UA_STATUSCODE_GOOD; - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, rg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; if(!el) { UA_LOG_ERROR_PUBSUB(server->config.logging, rg, "No EventLoop configured"); return UA_STATUSCODE_BADINTERNALERROR;; diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index 2b926344b..0dfbc79a8 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -574,12 +574,11 @@ UA_StatusCode UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm, UA_DataSetMessage *dataSetMessage, UA_DataSetWriter *dsw) { + UA_EventLoop *el = psm->sc.server->config.eventLoop; + /* Heartbeat message if no pds is connected */ UA_PublishedDataSet *pds = dsw->connectedDataSet; - UA_WriterGroup *wg = dsw->linkedWriterGroup; - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); - /* Reset the message */ memset(dataSetMessage, 0, sizeof(UA_DataSetMessage)); diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index ce9d75cc8..e48d3c5e6 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -65,7 +65,7 @@ UA_WriterGroup_addPublishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { return UA_STATUSCODE_GOOD; /* Use EventLoop for cyclic callbacks */ - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; return el->addTimer(el, (UA_Callback)UA_WriterGroup_publishCallback, psm, wg, wg->config.publishingInterval, NULL /* TODO: use basetime */, @@ -77,7 +77,7 @@ void UA_WriterGroup_removePublishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { if(wg->publishCallbackId == 0) return; - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; el->removeTimer(el, wg->publishCallbackId); wg->publishCallbackId = 0; } @@ -850,7 +850,7 @@ UA_WriterGroup_publishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { size_t enabledWriters = 0; UA_DataSetWriter *dsw; - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; LIST_FOREACH(dsw, &wg->writers, listEntry) { if(dsw->head.state != UA_PUBSUBSTATE_OPERATIONAL) continue; @@ -1167,7 +1167,7 @@ UA_WriterGroup_connect(UA_PubSubManager *psm, UA_WriterGroup *wg, if(wg->config.transportSettings.encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) return UA_STATUSCODE_GOOD; - UA_EventLoop *el = UA_PubSubConnection_getEL(psm, wg->linkedConnection); + UA_EventLoop *el = psm->sc.server->config.eventLoop; if(!el) { UA_LOG_ERROR_PUBSUB(psm->logging, wg, "No EventLoop configured"); UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_ERROR); From 7d179b53c4d170235523a159c95e896e98ceb971 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 21:45:58 +0100 Subject: [PATCH 148/158] fix(pubsub): Allow empty Variants and DataValues, but not for raw values --- src/pubsub/ua_pubsub_networkmessage_binary.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_networkmessage_binary.c b/src/pubsub/ua_pubsub_networkmessage_binary.c index dd9854a98..909b6d361 100644 --- a/src/pubsub/ua_pubsub_networkmessage_binary.c +++ b/src/pubsub/ua_pubsub_networkmessage_binary.c @@ -1200,14 +1200,15 @@ UA_DataSetMessage_keyFrame_encodeBinary(const UA_DataSetMessage* src, UA_Byte ** for(UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { const UA_DataValue *v = &src->data.keyFrameData.dataSetFields[i]; - if(!v->value.type) { - rv = UA_STATUSCODE_BADINTERNALERROR; - break; - } if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { rv = UA_Variant_encodeBinary(&v->value, bufPos, bufEnd); + } else if(src->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) { + rv = UA_DataValue_encodeBinary(v, bufPos, bufEnd); } else if(src->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) { + if(!v->value.type) + return UA_STATUSCODE_BADINTERNALERROR; + UA_FieldMetaData *fmd = &src->data.keyFrameData.dataSetMetaDataType->fields[i]; /* For arrays we need to encode the dimension sizes before the actual data */ @@ -1242,8 +1243,6 @@ UA_DataSetMessage_keyFrame_encodeBinary(const UA_DataSetMessage* src, UA_Byte ** } valuePtr += v->value.type->memSize; } - } else if(src->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) { - rv = UA_DataValue_encodeBinary(v, bufPos, bufEnd); } UA_CHECK_STATUS(rv, return rv); From 1709780d84132c40a6c18bab3675cc4106d65691 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 21:46:42 +0100 Subject: [PATCH 149/158] fix(server): A value backend does not always need a notification-callback --- src/server/ua_services_attribute.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/server/ua_services_attribute.c b/src/server/ua_services_attribute.c index f001692d3..ad981beee 100644 --- a/src/server/ua_services_attribute.c +++ b/src/server/ua_services_attribute.c @@ -246,17 +246,21 @@ readValueAttributeComplete(UA_Server *server, UA_Session *session, //TODO change old structure to value backend break; case UA_VALUEBACKENDTYPE_EXTERNAL: - if(!vn->valueBackend.backend.external.callback.notificationRead) { + if(vn->valueBackend.backend.external.callback.notificationRead) { + retval = vn->valueBackend.backend.external.callback. + notificationRead(server, + session ? &session->sessionId : NULL, + session ? session->context : NULL, + &vn->head.nodeId, vn->head.context, rangeptr); + if(retval != UA_STATUSCODE_GOOD) + break; + } + + /* Check that a value is available */ + if(!vn->valueBackend.backend.external.value) { retval = UA_STATUSCODE_BADNOTREADABLE; break; } - retval = vn->valueBackend.backend.external.callback. - notificationRead(server, - session ? &session->sessionId : NULL, - session ? session->context : NULL, - &vn->head.nodeId, vn->head.context, rangeptr); - if(retval != UA_STATUSCODE_GOOD) - break; /* Set the result */ if(rangeptr) From de4d4876d69498839e2b9d6aef63b1e0878ff1c4 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 21:53:54 +0100 Subject: [PATCH 150/158] refactor(tests): Remove redundant fastpath tests in check_pubsub_publisherid.c --- tests/pubsub/check_pubsub_publisherid.c | 215 ++---------------------- 1 file changed, 12 insertions(+), 203 deletions(-) diff --git a/tests/pubsub/check_pubsub_publisherid.c b/tests/pubsub/check_pubsub_publisherid.c index 2fe2c6a04..1c1049bd1 100644 --- a/tests/pubsub/check_pubsub_publisherid.c +++ b/tests/pubsub/check_pubsub_publisherid.c @@ -19,7 +19,6 @@ static UA_Server *server = NULL; UA_Logger logger; /* global variables for test configuration */ -static UA_Boolean UseFastPath = UA_FALSE; static UA_Boolean UseRawEncoding = UA_FALSE; static void setup(void) { @@ -116,20 +115,7 @@ AddPublishedDataSet(UA_NodeId *pWriterGroupId, char *pPublishedDataSetName, dataSetFieldConfig.field.variable.promotedField = UA_FALSE; dataSetFieldConfig.field.variable.publishParameters.publishedVariable = *opPublishedVarId; dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - if (UseFastPath) { - *oppFastPathPublisherDataValue = UA_DataValue_new(); - ck_assert(*oppFastPathPublisherDataValue != 0); - UA_Int32 *pPublisherData = UA_Int32_new(); - ck_assert(pPublisherData != 0); - *pPublisherData = 42; - UA_Variant_setScalar(&((**oppFastPathPublisherDataValue).value), pPublisherData, &UA_TYPES[UA_TYPES_INT32]); - /* add external value backend for fast-path */ - UA_ValueBackend valueBackend; - memset(&valueBackend, 0, sizeof(valueBackend)); - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = oppFastPathPublisherDataValue; - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_setVariableNode_valueBackend(server, *opPublishedVarId, valueBackend)); - } + UA_DataSetFieldResult PdsFieldResult = UA_Server_addDataSetField(server, *opPublishedDataSetId, &dataSetFieldConfig, &dataSetFieldId); ck_assert_int_eq(UA_STATUSCODE_GOOD, PdsFieldResult.result); @@ -221,21 +207,6 @@ AddDataSetReader(UA_NodeId *pReaderGroupId, char *pName, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, opSubscriberVarId)); - if (UseFastPath) { - *oppFastPathSubscriberDataValue = UA_DataValue_new(); - ck_assert(*oppFastPathSubscriberDataValue != 0); - UA_Int32 *pSubscriberData = UA_Int32_new(); - ck_assert(pSubscriberData != 0); - *pSubscriberData = 0; - UA_Variant_setScalar(&((**oppFastPathSubscriberDataValue).value), pSubscriberData, &UA_TYPES[UA_TYPES_INT32]); - /* add external value backend for fast-path */ - UA_ValueBackend valueBackend; - memset(&valueBackend, 0, sizeof(valueBackend)); - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = oppFastPathSubscriberDataValue; - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_setVariableNode_valueBackend(server, *opSubscriberVarId, valueBackend)); - } - UA_FieldTargetDataType targetVariable; UA_FieldTargetDataType_init(&targetVariable); targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; @@ -263,16 +234,11 @@ ValidatePublishSubscribe( UA_Int32 tmpValue = TestValue; for (UA_UInt32 i = 0; i < NoOfTestVars; i++) { tmpValue = TestValue + (UA_Int32) i; - if (UseFastPath) { - ck_assert((fastPathPublisherValues != 0) && (fastPathPublisherValues[i] != 0)); - *((UA_Int32 *) (fastPathPublisherValues[i]->value.data)) = tmpValue; - } else { - UA_Variant writeValue; - UA_Variant_init(&writeValue); - UA_Variant_setScalarCopy(&writeValue, &tmpValue, &UA_TYPES[UA_TYPES_INT32]); - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_writeValue(server, publisherVarIds[i], writeValue)); - UA_Variant_clear(&writeValue); - } + UA_Variant writeValue; + UA_Variant_init(&writeValue); + UA_Variant_setScalarCopy(&writeValue, &tmpValue, &UA_TYPES[UA_TYPES_INT32]); + ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_writeValue(server, publisherVarIds[i], writeValue)); + UA_Variant_clear(&writeValue); } UA_Boolean done = false; @@ -283,27 +249,18 @@ ValidatePublishSubscribe( UA_UInt32 i = 0; for(i = 0; i < NoOfTestVars; i++) { tmpValue = TestValue + (UA_Int32)i; - if(UseFastPath) { - ck_assert(fastPathSubscriberValues[i] != 0); - if(tmpValue != *((UA_Int32 *)fastPathSubscriberValues[i]->value.data)) { - done = false; - break; - } - } else { - UA_Variant SubscribedNodeData; - UA_Variant_init(&SubscribedNodeData); - UA_Server_readValue(server, subscriberVarIds[i], &SubscribedNodeData); - if(tmpValue != *(UA_Int32 *)SubscribedNodeData.data) - done = false; - UA_Variant_clear(&SubscribedNodeData); - } + UA_Variant SubscribedNodeData; + UA_Variant_init(&SubscribedNodeData); + UA_Server_readValue(server, subscriberVarIds[i], &SubscribedNodeData); + if(tmpValue != *(UA_Int32 *)SubscribedNodeData.data) + done = false; + UA_Variant_clear(&SubscribedNodeData); } } } static void DoTest_1_Connection(UA_PublisherId publisherId) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "DoTest_1_Connection() begin"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "fast-path = %s", (UseFastPath) ? "enabled" : "disabled"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "raw encoding = %s", (UseRawEncoding) ? "enabled" : "disabled"); #define DOTEST_1_CONNECTION_MAX_VARS 1 @@ -345,28 +302,6 @@ static void DoTest_1_Connection(UA_PublisherId publisherId) { &subscriberVarIds[0], &fastPathSubscriberDataValues[0], &DSRId_Conn1_RG1_DSR1); - /* string PublisherId is not supported with fast-path */ - if(UseFastPath && publisherId.idType == UA_PUBLISHERIDTYPE_STRING) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "test case: STRING publisherId with fast-path"); - - /* cleanup and continue with other tests */ - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePubSubConnection(server, ConnId_1)); - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, PDSId_Conn1_WG1_PDS1)); - - /* Iterate so the connections are actually deleted */ - UA_Server_run_iterate(server, false); - - for (UA_UInt32 i = 0; i < DOTEST_1_CONNECTION_MAX_VARS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - - return; - } - /* set groups operational */ ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); @@ -392,15 +327,6 @@ static void DoTest_1_Connection(UA_PublisherId publisherId) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "remove PDS"); ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, PDSId_Conn1_WG1_PDS1)); - if (UseFastPath) { - for (UA_UInt32 i = 0; i < DOTEST_1_CONNECTION_MAX_VARS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - } - /* Iterate so the connections are actually deleted */ UA_Server_run_iterate(server, false); @@ -418,19 +344,9 @@ START_TEST(Test_1_connection) { publisherId.idType = UA_PUBLISHERIDTYPE_BYTE; publisherId.id.byte = 2; - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_1_Connection(publisherId); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_1_Connection(publisherId); @@ -439,19 +355,9 @@ START_TEST(Test_1_connection) { publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; publisherId.id.uint16 = 3; - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_1_Connection(publisherId); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_1_Connection(publisherId); @@ -460,19 +366,9 @@ START_TEST(Test_1_connection) { publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; publisherId.id.uint32 = 5; - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_1_Connection(publisherId); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_1_Connection(publisherId); @@ -481,19 +377,9 @@ START_TEST(Test_1_connection) { publisherId.idType = UA_PUBLISHERIDTYPE_UINT64; publisherId.id.uint64 = 6; - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_1_Connection(publisherId); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_1_Connection(publisherId); @@ -502,20 +388,9 @@ START_TEST(Test_1_connection) { publisherId.idType = UA_PUBLISHERIDTYPE_STRING; publisherId.id.string = UA_STRING("My PublisherId"); - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_1_Connection(publisherId); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_1_Connection(publisherId); - - /* Note: STRING publisherId is not supported with fast-path */ - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_1_Connection(publisherId); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_1_Connection(publisherId); @@ -527,7 +402,6 @@ START_TEST(Test_1_connection) { static void DoTest_multiple_Connections(void) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "DoTest_multiple_Connections() begin"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "fast-path = %s", (UseFastPath) ? "enabled" : "disabled"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "raw encoding = %s", (UseRawEncoding) ? "enabled" : "disabled"); /* Writers -> Readers @@ -814,16 +688,6 @@ static void DoTest_multiple_Connections(void) { PublishedDataSetIds[i])); } - if (UseFastPath) { - for (size_t i = 0; i < DOTEST_MULTIPLE_CONNECTIONS_MAX_COMPONENTS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - } - /* Iterate so the connections are actually deleted */ UA_Server_run_iterate(server, false); @@ -839,19 +703,9 @@ START_TEST(Test_multiple_connections) { - multiple groups and/or DataSets yet, therefore we only test multiple connections - STRING publisherIds */ - UseFastPath = UA_FALSE; UseRawEncoding = UA_FALSE; DoTest_multiple_Connections(); - UseFastPath = UA_FALSE; - UseRawEncoding = UA_TRUE; - DoTest_multiple_Connections(); - - UseFastPath = UA_TRUE; - UseRawEncoding = UA_FALSE; - DoTest_multiple_Connections(); - - UseFastPath = UA_TRUE; UseRawEncoding = UA_TRUE; DoTest_multiple_Connections(); @@ -904,7 +758,6 @@ Test_string_PublisherId_InformationModel(const UA_NodeId connectionId, static void DoTest_string_PublisherId(void) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "DoTest_string_PublisherId() begin"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "fast-path = %s", (UseFastPath) ? "enabled" : "disabled"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "raw encoding = %s", (UseRawEncoding) ? "enabled" : "disabled"); /* Writers -> Readers @@ -1163,16 +1016,6 @@ static void DoTest_string_PublisherId(void) { ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, PublishedDataSetIds[i])); } - if (UseFastPath) { - for (size_t i = 0; i < DOTEST_STRING_PUBLISHERID_MAX_COMPONENTS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - } - /* Iterate so the connections are actually deleted */ UA_Server_run_iterate(server, false); @@ -1185,9 +1028,6 @@ static void DoTest_string_PublisherId(void) { START_TEST(Test_string_publisherId) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "START: Test_string_publisherId"); - /* note: fast-path does not support STRING publisherIds */ - UseFastPath = UA_FALSE; - UseRawEncoding = UA_FALSE; DoTest_string_PublisherId(); @@ -1205,7 +1045,6 @@ START_TEST(Test_string_publisherId_file_config) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "START: Test_string_publisherId_file_config"); UseRawEncoding = UA_FALSE; - UseFastPath = UA_FALSE; #define STRING_PUBLISHERID_FILE_MAX_COMPONENTS 1 /* Attention: Publisher and corresponding Subscriber NodeId and DataValue must have the same index @@ -1456,7 +1295,6 @@ START_TEST(Test_string_publisherId_file_config) { static void DoTest_multiple_Groups(void) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "DoTest_multiple_Groups() begin"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "fast-path = %s", (UseFastPath) ? "enabled" : "disabled"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "raw encoding = %s", (UseRawEncoding) ? "enabled" : "disabled"); /* Writers -> Readers -> Var Index @@ -1787,16 +1625,6 @@ static void DoTest_multiple_Groups(void) { ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, PublishedDataSetIds[i])); } - if (UseFastPath) { - for (size_t i = 0; i < DOTEST_MULTIPLE_GROUPS_MAX_VARS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - } - /* Iterate so the connections are actually deleted */ UA_Server_run_iterate(server, false); @@ -1807,10 +1635,6 @@ static void DoTest_multiple_Groups(void) { START_TEST(Test_multiple_groups) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "START: Test_multiple_groups"); - /* note: fast-path does not multiple groups/datasets/string publisher Ids - therefore fast-path is not enabled at this test */ - UseFastPath = UA_FALSE; - UseRawEncoding = UA_FALSE; DoTest_multiple_Groups(); @@ -1825,7 +1649,6 @@ START_TEST(Test_multiple_groups) { static void DoTest_multiple_DataSets(void) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "DoTest_multiple_DataSets() begin"); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "fast-path = %s", (UseFastPath) ? "enabled" : "disabled"); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "raw encoding = %s", (UseRawEncoding) ? "enabled" : "disabled"); /* Writers -> Readers -> Var Index @@ -2052,16 +1875,6 @@ static void DoTest_multiple_DataSets(void) { ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_removePublishedDataSet(server, PublishedDataSetIds[i])); } - if (UseFastPath) { - for (size_t i = 0; i < DOTEST_MULTIPLE_DATASETS_MAX_VARS; i++) { - UA_DataValue_clear(fastPathPublisherDataValues[i]); - UA_DataValue_delete(fastPathPublisherDataValues[i]); - - UA_DataValue_clear(fastPathSubscriberDataValues[i]); - UA_DataValue_delete(fastPathSubscriberDataValues[i]); - } - } - /* Iterate so the connections are actually deleted */ UA_Server_run_iterate(server, false); @@ -2072,10 +1885,6 @@ static void DoTest_multiple_DataSets(void) { START_TEST(Test_multiple_datasets) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "START: Test_multiple_datasets"); - /* note: fast-path does not multiple groups/datasets/string publisher Ids - therefore fast-path is not enabled at this test */ - UseFastPath = UA_FALSE; - UseRawEncoding = UA_FALSE; DoTest_multiple_DataSets(); From 46c110085f12dc2934669d6681a5099bb311aca2 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 23:00:11 +0100 Subject: [PATCH 151/158] refactor(examples): Remove attic-folder for old PubSub-RT examples --- examples/pubsub_realtime/attic/README.md | 78 - .../attic/README_pubsub_TSN.txt | 188 -- .../pubsub_realtime/attic/Readme_PubSub.txt | 195 -- .../attic/nodeset/CMakeLists.txt | 49 - .../attic/nodeset/pubDataModel.csv | 4 - .../attic/nodeset/pubDataModel.xml | 80 - .../nodeset/pubsub_nodeset_rt_publisher.c | 736 ------- .../nodeset/pubsub_nodeset_rt_subscriber.c | 656 ------ .../attic/nodeset/subDataModel.csv | 4 - .../attic/nodeset/subDataModel.xml | 80 - .../attic/pubsub_TSN_loopback.c | 1742 ---------------- .../attic/pubsub_TSN_loopback_single_thread.c | 1408 ------------- .../attic/pubsub_TSN_publisher.c | 1814 ----------------- .../pubsub_TSN_publisher_multiple_thread.c | 1505 -------------- .../attic/pubsub_interrupt_publish.c | 380 ---- ...server_pubsub_rt_field_information_model.c | 194 -- .../pubsub_realtime/attic/start_rt_publish.sh | 51 - .../pubsub_realtime/attic/vxworks/README.md | 103 - .../vxworks/pubsub_interrupt_publish_tsn.c | 630 ------ 19 files changed, 9897 deletions(-) delete mode 100644 examples/pubsub_realtime/attic/README.md delete mode 100644 examples/pubsub_realtime/attic/README_pubsub_TSN.txt delete mode 100644 examples/pubsub_realtime/attic/Readme_PubSub.txt delete mode 100644 examples/pubsub_realtime/attic/nodeset/CMakeLists.txt delete mode 100644 examples/pubsub_realtime/attic/nodeset/pubDataModel.csv delete mode 100644 examples/pubsub_realtime/attic/nodeset/pubDataModel.xml delete mode 100644 examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c delete mode 100644 examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c delete mode 100644 examples/pubsub_realtime/attic/nodeset/subDataModel.csv delete mode 100644 examples/pubsub_realtime/attic/nodeset/subDataModel.xml delete mode 100644 examples/pubsub_realtime/attic/pubsub_TSN_loopback.c delete mode 100644 examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c delete mode 100644 examples/pubsub_realtime/attic/pubsub_TSN_publisher.c delete mode 100644 examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c delete mode 100644 examples/pubsub_realtime/attic/pubsub_interrupt_publish.c delete mode 100644 examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c delete mode 100755 examples/pubsub_realtime/attic/start_rt_publish.sh delete mode 100644 examples/pubsub_realtime/attic/vxworks/README.md delete mode 100644 examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c diff --git a/examples/pubsub_realtime/attic/README.md b/examples/pubsub_realtime/attic/README.md deleted file mode 100644 index b6b7fb5d7..000000000 --- a/examples/pubsub_realtime/attic/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# open62541 Realtime OPC UA PubSub Publisher - -This example is a self-contained PubSub publisher over raw Ethernet. It -showcases the realtime-capabilities of OPC UA PubSub. The core idea is that the -publisher callback can be triggered from a time-triggered system interrupt and -sends out the PubSub message within the interrupt. - -The publisher retrieves its configuration and the payload data from the -information model of an OPC UA server. (It could also be run in a standalone -mode without an OPC UA server.) Since the publisher interrupt preempts the -execution of the normal OPC UA server, the information model needs to be -consistent at every time (reentrant). The specific techniques used to make the -OPC UA server reentrant are described in this publication: - -``` -@inproceedings{pfrommer2018open, - title={Open source OPC UA PubSub over TSN for realtime industrial communication}, - author={Pfrommer, Julius and Ebner, Andreas and Ravikumar, Siddharth and Karunakaran, Bhagath}, - booktitle={2018 IEEE 23rd International Conference on Emerging Technologies and Factory Automation (ETFA)}, - pages={1087--1090}, - year={2018}, - organization={IEEE} -} -``` - -Please cite if you use this work. - -OPC UA PubSub for open62541 is funded by an industry consortium in the context -of an OSADL project (Open Source Automation Development Lab). Technical -development is conducted by Fraunhofer IOSB and Kalycito Infotech. - -https://www.osadl.org/OPC-UA-TSN.opcua-tsn.0.html - -## Realtime communication with Time-Sensitive Networking (TSN) - -OPC UA PubSub can be used together with TSN for hard-realtime Ethernet-based -communication. Vendor-specific APIs are commonly used for TSN capabilities. This -example only uses the standard Linux API for raw Ethernet. Vendor-specific -examples may be added at a later time. - -## Building the RT Publisher - -The main open62541 library needs to be built with the following build options -enabled for the realtime PubSub example. Note that some of the other examples -supplied with open62541 will not link against the library with these build -options. For good timings, ensure that the `CMAKE_BUILD_TYPE` is set to -`Release`. - -- UA_ENABLE_PUBSUB -- UA_BUILD_EXAMPLES -- UA_ENABLE_MALLOC_SINGLETON -- UA_ENABLE_PUBSUB_BUFMALLOC -- UA_ENABLE_IMMUTABLE_NODES - -The publisher contains some hard-coded values that need to be adjusted to -specific systems. Please check the top definitions in -`pubsub_interrupt_publish.c` and `start_rt_publish.sh`. - -## Running the RT Publisher - -The publisher must be run as root for direct access to the Ethernet interface. - -`# ./rt_publisher` - -The example contains a script to be used with RT-Preempt Linux kernel. The -following command starts the publisher, locks the process to a specific CPU, and -sets the scheduling policy. - -`# start_rt_publish.sh ./rt_publisher` - -The measurements are written to a file (publisher_measurement.csv) with the -following fields for every publish callback: - -- Counter -- Publication Interval -- Nominal time for the current publish -- Start delay from the nominal time -- Duration of the publish callback diff --git a/examples/pubsub_realtime/attic/README_pubsub_TSN.txt b/examples/pubsub_realtime/attic/README_pubsub_TSN.txt deleted file mode 100644 index aa3a14440..000000000 --- a/examples/pubsub_realtime/attic/README_pubsub_TSN.txt +++ /dev/null @@ -1,188 +0,0 @@ -Tested Environment: -Apollo Lake processor-1.60GHz with Intel i210 Ethernet Controller -OS - Debian/Lubuntu -Kernel - 4.19.37-rt19, 5.4.59-rt36 -[Note: The pubsub TSN applications have two functionalities - ETF and XDP. - ETF will work in all RT kernels above 4.19. XDP is tested only on 5.4.59-rt36 kernel and it may work on kernels above 5.4. - If user wishes to run these pubsub TSN applications in the kernel below 4.19, there might be minimal changes required in the applications] - -PRE-REQUISITES: -We recommend at least two Intel x86-based nodes with 4-cores and Intel i210 Ethernet Controllers connected in peer-peer fashion -RT enabled kernel in nodes (say node1 and node2) -Ensure the nodes are ptp synchronized -PTP SYNCHRONIZATION: -Clone and install linuxptp version 2.0 - git clone https://github.com/richardcochran/linuxptp.git - cd linuxptp/ - git checkout -f 059269d0cc50f8543b00c3c1f52f33a6c1aa5912 - make - make install - cp configs/gPTP.cfg /etc/linuxptp/ - -Create VLAN with VLAN ID of 8 in peer to peer connected interface, copy paste the following lines in "/etc/network/interfaces" file and setup suitable IP address - auto .8 - iface .8 inet static - address - netmask 255.255.255.0 - -To make node as ptp master run the following command (say in node1): - sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i -2 -mq -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1 -To make node as ptp slave run the following command (say in node2): - sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i -2 -mq -s -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1 -To ensure phc2sys synchronization (in both nodes, node1 and node2): - sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/phc2sys.log -e /var/log/phc2sys.err.log /usr/bin/taskset -c 1 chrt 89 /usr/local/sbin/phc2sys -s -c CLOCK_REALTIME --step_threshold=1 --transportSpecific=1 -w -m - -Check if ptp4l and phc2sys are running - ptp4l log in /var/log/ptp4l.log - phc2sys log in /var/log/phc2sys.log - -Check if /etc/modules has entry for 8021q - cat /etc/modules | grep '8021q' - -For ETF Transmit: (In both nodes) - #Configure ETF - sudo tc qdisc add dev parent root mqprio num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 0 - MQPRIO_NUM=`sudo tc qdisc show | grep mqprio | cut -d ':' -f1 | cut -d ' ' -f3` - sudo tc qdisc add dev parent $MQPRIO_NUM:1 etf offload clockid CLOCK_TAI delta 150000 - sudo tc qdisc add dev parent $MQPRIO_NUM:2 etf offload clockid CLOCK_TAI delta 150000 - -In both nodes: - for j in `seq 0 7`;do sudo ip link set .8 type vlan egress $j:$j ; done - for j in `seq 0 7`;do sudo ip link set .8 type vlan ingress $j:$j ; done - -TO RUN ETF APPLICATIONS: -To run ETF applications over Ethernet in two nodes connected in peer-to-peer network - mkdir build - cd build - cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON .. - make - -The generated binaries are generated in build/bin/ folder - ./bin/examples/pubsub_TSN_publisher -interface - Run in node 1 - ./bin/examples/pubsub_TSN_loopback -interface - Run in node 2 -Eg: ./bin/examples/pubsub_TSN_publisher -interface enp2s0 - ./bin/examples/pubsub_TSN_loopback -interface enp2s0 - -NOTE: It is always recommended to run pubsub_TSN_loopback application first to avoid missing the initial data published by pubsub_TSN_publisher - -To know more usage - ./bin/examples/pubsub_TSN_publisher -help - ./bin/examples/pubsub_TSN_loopback -help - -NOTE: To know more about running the OPC UA PubSub application and to evaluate performance, refer the Quick Start Guide - https://www.kalycito.com/how-to-run-opc-ua-pubsub-tsn/ -============================================================================================================ -You can also subscribe using XDP (Express Data Path) for faster processing the data, follow the below steps: - -Pre-requisties for XDP: - Minimum kernel version of 5.4 (Tested in RT enabled kernel - 5.4.59-rt36 with Debian OS) - Install llvm and clang - apt-get install llvm - apt-get install clang - Clone libbpf using the following steps: - git clone https://github.com/libbpf/libbpf.git - cd libbpf/ - git checkout ab067ed3710550c6d1b127aac6437f96f8f99447 - cd src/ - OBJDIR=/usr/lib make install - - Check if libbpf.so is available in /usr/lib folder, and bpf/ folder is available in /usr/include folder in both nodes (node1 and node2) - -NOTE: Make sure the header file if_xdp.h available in /usr/include/linux/ is the right header file as in kernel version 5.4.59, if not update the if_xdp.h to newer version. (Verify your if_xdp.h file with https://elixir.bootlin.com/linux/v5.4.36/source/include/uapi/linux/if_xdp.h) - -As per the sample application, XDP listens to Rx_Queue 2; to direct the incoming ethernet packets to the second Rx_Queue, the follow the given steps - In both nodes: - INGRESS POLICY steps: - #Configure equal weightage to queue 0 and 1 - ethtool -X equal 2 - #Disable VLAN offload - ethtool -K rxvlan off - ethtool -K ntuple on - ethtool --config-ntuple delete 15 - ethtool --config-ntuple delete 14 - #Below command forwards the VLAN tagged packets to RX_Queue 2 - ethtool --config-ntuple flow-type ether proto 0x8100 dst 01:00:5E:7F:00:01 loc 15 action 2 - ethtool --config-ntuple flow-type ether proto 0x8100 dst 01:00:5E:7F:00:02 loc 14 action 2 - -To run ETF in Publisher and XDP in Subscriber in two nodes connected in peer-to-peer network - cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON .. - make - By default XDP will be disabled in the Subscriber, use -enableXdpSubscribe to use XDP in RX. - ./bin/examples/pubsub_TSN_publisher -interface -enableXdpSubscribe - Run in node 1 - ./bin/examples/pubsub_TSN_loopback -interface -enableXdpSubscribe - Run in node 2 - -Optional steps: - By default, XDP listens to RX_Queue 2 with SKB and COPY mode. - To make XDP listen to other RX_Queue, provide -xdpQueue as arguments when executing applications and modify the action value (INGRESS POLICY) to the desired queue number. - To enable ZEROCOPY mode, provide -xdpBindFlagZeroCopy as an additional argument to the applications - To enable XDP Driver (DRV) mode, provide -xdpFlagDrvMode as an additional argument to the applications - -NOTE: To enable XDP socket, pass its configuration parameters through connectionProperties (KeyValuePair) in connectionConfig as mentioned below. XDP socket has support -for both Publisher and Subscriber connection but it is recommended to use XDP only in Subscriber connection. - -UA_KeyValuePair connectionOptions[4]; // KeyValuePair for XDP configuration -connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); // Key for enabling the XDP socket -UA_Boolean enableXdp = UA_TRUE; // Boolean value for enabling and disabling XDP socket -UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); -connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); // Key for the XDP flags -UA_UInt32 flags = xdpFlag; // Value to determine whether XDP works in SKB mode or DRV mode -UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]); -connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); // Key for the hardware queue -UA_UInt32 rxqueue = xdpQueue; // Value of the hardware queue to receive the packets -UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); -connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); // Key for the XDP bind flags -UA_UInt32 bindflags = xdpBindFlag; // Value to determine whether XDP works in COPY or ZEROCOPY mode -UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]); -connectionConfig.connectionProperties = connectionOptions; // Provide all the KeyValuePairs to properties of connectionConfig -connectionConfig.connectionPropertiesSize = 4; - -To know more usage - ./bin/examples/pubsub_TSN_publisher -help - ./bin/examples/pubsub_TSN_loopback -help -=================================================================================================================================================================== -NOTE: -If VLAN tag is used: - Ensure the MAC address is given along with VLAN ID and PCP - Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP -If VLAN tag is not used: - Ensure the MAC address is not given along with VLAN ID and PCP - Eg: "opc.eth://01-00-5E-00-00-01" -If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes - -To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher.c and pubsub_TSN_loopback.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload) - -===================================================================================================================================================================== -TO RUN THE UNIT TEST CASES FOR ETHERNET FUNCTIONALITY: - -Change the ethernet interface in #define ETHERNET_INTERFACE macro in - check_pubsub_connection_ethernet.c - check_pubsub_publish_ethernet.c - check_pubsub_connection_ethernet_etf.c - check_pubsub_publish_ethernet_etf.c - -1. To test ethernet connection creation and ethernet publish unit tests(without realtime) - cd open62541/build - make clean - cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON .. - make - The following binaries are generated in build/bin/tests folder - ./bin/tests/check_pubsub_connection_ethernet - To check ethernet connection creation - ./bin/tests/check_pubsub_publish_ethernet - To check ethernet send functionality - -2. To test ethernet connection creation and ethernet publish unit tests(with realtime) - cd open62541/build - make clean - cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON .. - make - The following binaries are generated in build/bin/tests folder - ./bin/tests/check_pubsub_connection_ethernet_etf - To check ethernet connection creation with etf - ./bin/tests/check_pubsub_publish_ethernet_etf - To check ethernet send functionality with etf - -TO RUN THE UNIT TEST CASES FOR XDP FUNCTIONALITY: - -1. To test connection creation using XDP transport layer - cd open62541/build - make clean - cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON .. - make - The following binary is generated in build/bin/tests folder - ./bin/tests/check_pubsub_connection_xdp - To check connection creation with XDP diff --git a/examples/pubsub_realtime/attic/Readme_PubSub.txt b/examples/pubsub_realtime/attic/Readme_PubSub.txt deleted file mode 100644 index e61fa4ef7..000000000 --- a/examples/pubsub_realtime/attic/Readme_PubSub.txt +++ /dev/null @@ -1,195 +0,0 @@ -Tested Environment: -TTTech IP (2.3.0) - Arm architecture -Kernel - 5.4.40 (non-RT) -============================================================================================================ -PRE-REQUISITES: -1) We recommend at least two TTTech TSN IP nodes with 2-cores - -2) Ensure the nodes are ptp synchronized - deptp_tool --get-current-dataset - -3) Add and aditional clock domain /etc/deptp/ptp_config.xml, reboot the board after applying this change (this is a one time step) - - 187 - 1us - 247 - 248 - 100 - internal oscillator - - -4) Create VLAN with VLAN ID of 8 in peer to peer connected interface using the below command - ip link add link name myvlan type vlan id 8 ingress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7 egress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7 - ip addr add dev myvlan - ip link set dev myvlan up - bridge vlan add vid 8 dev - bridge vlan add vid 8 dev (optional e.g. for monitoring) - bridge vlan add vid 8 dev - ip route add 224.0.0.0/24 dev myvlan - - NOTE: refers to the external physical port that has ethernet cable connected to it in each node - -5) Create the Qbv configuration i.e setting up the gating configuration: - 1)Create a config file on both the nodes using the below command - touch qbv.cfg - 2)At node 1 copy the below gating configuration and paste it in the qbv.cfg - sgs 5000 0x80 - sgs 490000 0x7F - 3)At node 2 copy the below gating configuration and paste it in the qbv.cfg - sgs 5000 0x80 - sgs 490000 0x7F - 4)To schedule the gating configuration run the below command on both the nodes - tsntool st wrcl qbv.cfg - tsntool st rdacl /dev/stdout - tsntool st configure 5.0 1/2000 10000 - tsntool st show - -6) After these steps the following files shoud exist and have values: - cat /run/ptp_wc_mon_offset - cat /sys/class/net//ieee8021ST/OperBaseTime -============================================================================================================ -Cross Compiler options to compile: -1) To compile the binaries create the folder using the below command: - mkdir build - -2) Traverse to the folder using the command - cd build - -3) Install the arm cross compiler - arm-linux-gnueabihf - -4) Cross compilation for ARM architecture - CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc - CMAKE_C_COMPILER_AR /usr/bin/arm-linux-gnueabihf-gcc-ar-7 - CMAKE_C_COMPILER_RANLIB /usr/bin/arm-linux-gnueabihf-gcc-ranlib-7 - CMAKE_C_FLAGS -Wno-error - CMAKE_C_FLAGS_DEBUG -g -Wno-error - CMAKE_C_LINKER /usr/bin/arm-linux-gnueabihf-ld - CMAKE_NM /usr/bin/arm-linux-gnueabihf-nm - CMAKE_OBJCOPY /usr/bin/arm-linux-gnueabihf-objcopy - CMAKE_OBJDUMP /usr/bin/arm-linux-gnueabihf-objdump - CMAKE_RANLIB /usr/bin/arm-linux-gnueabihf-ranlib - CMAKE_STRIP /usr/bin/arm-linux-gnueabihf-strip - - Note: The above mentioned options are changes for arm compiler.Ignore them if you - are compiling for x86. The options will be available in tools/cmake/Toolchain-ARM.cmake - for cross compilation - - CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread): - cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON .. - make -j4 pubsub_TSN_publisher_multiple_thread - - CMake options for loopback application(pubsub_TSN_loopback_single_thread): - cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON .. - make -j4 pubsub_TSN_loopback_single_thread - -5) Compilation for x86 architecture - CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread): - cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON .. - make -j4 pubsub_TSN_publisher_multiple_thread - - CMake options for loopback application(pubsub_TSN_loopback_single_thread): - cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON .. - make -j4 pubsub_TSN_loopback_single_thread - -============================================================================================================ -To RUN the APPLICATIONS: -The generated binaries are generated in build/bin/ folder -============================================================================================================ -TWO WAY COMMUNICATION -============================================================================================================ - - For Ethernet(Without logs) For long run: - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2 - - For Ethernet:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2 - - Note: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread - - For UDP(Without logs) For long run: - For UDP only one change need to be done before running that is need to provide the interface ip address as an input for -interface option - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2 - - For UDP:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2 - - NOTE: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run - change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread -============================================================================================================ -ONE WAY COMMUNICATION: For one way communication disable(comment) the macro TWO_WAY_COMMUNICATION in the the pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread -============================================================================================================ - - Run steps for Ethernet: - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2 - - Run steps for UDP: - ./pubsub_TSN_publisher_multiple_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1 - - ./pubsub_TSN_loopback_single_thread -interface -disableSoTxtime -operBaseTime /sys/class/net//ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2 - -NOTE: As we have mentioned previously for long run remove the option -enableCsvLog -============================================================================================================ -NOTE: It is always recommended to run pubsub_TSN_loopback_single_thread application first to avoid missing the initial data published by pubsub_TSN_publisher_multiple_thread - -To know more usage - ./bin/examples/pubsub_TSN_publisher_multiple_thread -help - ./bin/examples/pubsub_TSN_loopback_single_thread -help -============================================================================================================ -NOTE: -For Ethernet -If VLAN tag is used: - Ensure the MAC address is given along with VLAN ID and PCP - Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP -If VLAN tag is not used: - Ensure the MAC address is not given along with VLAN ID and PCP - Eg: "opc.eth://01-00-5E-00-00-01" -If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes - -To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher_multiple_thread.c and pubsub_TSN_loopback_single_thread.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload) - -============================================================================================================ -Output Logs: If application runs with option enableCsvLog -/** - * Trace point setup - * - * +--------------+ +----------------+ - * T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4 - * | | Application | ^ | | Application | ^ - * | +--------------+ | | +----------------+ | - * User | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * ----------|--------------|------------------------|----------------|------- - * | | Node 1 | | | | Node 2 | | - * Kernel| | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * v +--------------+ | v +----------------+ | - * T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3 - * | +--------------+ +----------------+ ^ - * | | - * ---------------------------------------------------------------- - */ -For publisher application (pubsub_TSN_publisher_multiple_thread): publisher_T1.csv, subscriber_T8.csv -For loopback application: publisher_T5.csv, subscriber_T4.csv - -To Compute the round trip time: Subtract T8-T1 (subtract the time in the .csv files of subscriber_T8.csv - publisher_T1.csv) -============================================================================================================ -To close the running application press ctrl+c during the application exit the packet loss count of the entire run will be displayed in the console print. - -============================================================================================================ - -For the TTTech TSN IP version 2.3.0 the following are applicable: - sw0ep - available external physical ports: sw0p2, sw0p3, sw0p5, sw0p3 - sw0p1 diff --git a/examples/pubsub_realtime/attic/nodeset/CMakeLists.txt b/examples/pubsub_realtime/attic/nodeset/CMakeLists.txt deleted file mode 100644 index dfeaffb9d..000000000 --- a/examples/pubsub_realtime/attic/nodeset/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -#################### -# Nodeset Examples PubSub# -#################### - -################### -# Custom XML # -################### - -set(FILE_CSV_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset) -set(FILE_BSD_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset) -set(FILE_NS_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset) - -get_target_property(OPEN62541_BIN_DIR open62541::open62541 BINARY_DIR) -set(PROJECT_BINARY_DIR ${OPEN62541_BIN_DIR}) - -# generate namespace from XML file -ua_generate_nodeset_and_datatypes( - NAME "example_publisher" - FILE_NS "${FILE_NS_DIRPREFIX}/pubDataModel.xml" - DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml" -) -ua_generate_nodeset_and_datatypes( - NAME "example_subscriber" - FILE_NS "${FILE_NS_DIRPREFIX}/subDataModel.xml" - DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml" -) -# The .csv file can be created from within UaModeler or manually -ua_generate_nodeid_header( - NAME "example_nodeids_publisher" - ID_PREFIX "EXAMPLE_NS_PUBLISHER" - TARGET_SUFFIX "ids_example_publisher" - FILE_CSV "${FILE_CSV_DIRPREFIX}/pubDataModel.csv" -) -ua_generate_nodeid_header( - NAME "example_nodeids_subscriber" - ID_PREFIX "EXAMPLE_NS_SUBSCRIBER" - TARGET_SUFFIX "ids_example_subscriber" - FILE_CSV "${FILE_CSV_DIRPREFIX}/subDataModel.csv" -) - -add_example(pubsub_nodeset_rt_publisher pubsub_nodeset_rt_publisher.c - ${UA_NODESET_EXAMPLE_PUBLISHER_SOURCES} - ${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_publisher.h) -add_example(pubsub_nodeset_rt_subscriber pubsub_nodeset_rt_subscriber.c - ${UA_NODESET_EXAMPLE_SUBSCRIBER_SOURCES} - ${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_subscriber.h) -target_link_libraries(pubsub_nodeset_rt_publisher rt pthread) -target_link_libraries(pubsub_nodeset_rt_subscriber rt pthread) - diff --git a/examples/pubsub_realtime/attic/nodeset/pubDataModel.csv b/examples/pubsub_realtime/attic/nodeset/pubDataModel.csv deleted file mode 100644 index 29f6faf6c..000000000 --- a/examples/pubsub_realtime/attic/nodeset/pubDataModel.csv +++ /dev/null @@ -1,4 +0,0 @@ -PublisherInfo,2001,ObjectType -Publisher,2004,Object -PublisherCounterValue,2005,Variable -Pressure,2006,Variable diff --git a/examples/pubsub_realtime/attic/nodeset/pubDataModel.xml b/examples/pubsub_realtime/attic/nodeset/pubDataModel.xml deleted file mode 100644 index 7a6ceeb6a..000000000 --- a/examples/pubsub_realtime/attic/nodeset/pubDataModel.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - http://yourorganisation.org/test/ - - - i=37 - i=9 - i=11 - i=35 - i=40 - i=45 - i=47 - - - PublisherInfo - PublisherInfo - - i=58 - ns=1;i=2002 - ns=1;i=2003 - - - - PublisherCounterVariable - PublisherCounterVariable - - ns=1;i=2001 - i=63 - i=78 - - - 0 - - - - Pressure - Pressure - - ns=1;i=2001 - i=63 - i=78 - - - 0.0 - - - - Publisher - PublisherInfo - - i=85 - ns=1;i=2001 - ns=1;i=2005 - ns=1;i=2006 - - - - PublisherCounterVariable - PublisherCounterVariable - - ns=1;i=2004 - i=63 - - - 0 - - - - Pressure - Pressure - - ns=1;i=2004 - i=63 - - - 0.0 - - - diff --git a/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c b/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c deleted file mode 100644 index 432d39f3b..000000000 --- a/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c +++ /dev/null @@ -1,736 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-nodeset-tutorial: - * - * Publisher Realtime example using custom nodes - * --------------------------------------------- - * - * The purpose of this example file is to use the custom nodes of the XML - * file(pubDataModel.xml) for publisher. This Publisher example uses the two - * custom nodes (PublisherCounterVariable and Pressure) created using the XML - * file(pubDataModel.xml) for publishing the packet. The pubDataModel.csv will - * contain the nodeids of custom nodes(object and variables) and the nodeids of - * the custom nodes are harcoded inside the addDataSetField API. This example - * uses two threads namely the Publisher and UserApplication. The Publisher - * thread is used to publish data at every cycle. The UserApplication thread - * serves the functionality of the Control loop, which increments the - * counterdata to be published by the Publisher and also writes the published - * data in a csv along with transmission timestamp. - * - * Run steps of the Publisher application as mentioned below: - * - * ``./bin/examples/pubsub_nodeset_rt_publisher -i `` - * - * For more information run ``./bin/examples/pubsub_nodeset_rt_publisher -h``. */ - -#define _GNU_SOURCE - -/* For thread operations */ -#include - -#include -#include -#include -#include - -#include "ua_pubsub.h" -#include "open62541/namespace_example_publisher_generated.h" - -/* to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_publisher */ - -/* Configurable Parameters */ -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define QBV_OFFSET 25 * 1000 -#define DEFAULT_SOCKET_PRIORITY 3 -#define PUBLISHER_ID 2234 -#define WRITER_GROUP_ID 101 -#define DATA_SET_WRITER_ID 62541 -#define PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" -#define PORT_NUMBER 62541 - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000 * 1000 -#define SECONDS 1000 * 1000 * 1000 -#define SECONDS_SLEEP 5 -#define DEFAULT_PUB_SCHED_PRIORITY 78 -#define DEFAULT_PUBSUB_CORE 2 -#define DEFAULT_USER_APP_CORE 3 -#define MAX_MEASUREMENTS 30000000 -#define SECONDS_INCREMENT 1 -#define CLOCKID CLOCK_TAI -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75 - -/* Below mentioned parameters can be provided as input using command line arguments - * If user did not provide the below mentioned parameters as input through command line - * argument then default value will be used */ -static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME; -static UA_Boolean consolePrint = UA_FALSE; -static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY; -static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY; -static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY; -static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE; -static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE; -static UA_Boolean useSoTxtime = UA_TRUE; - -/* User application Pub will wakeup at the 30% of cycle time and handles the */ -/* user data write in Information model */ -/* First 30% is left for subscriber for future use*/ -static UA_Double userAppWakeupPercentage = 0.3; -/* Publisher will sleep for 60% of cycle time and then prepares the */ -/* transmission packet within 40% */ -/* after some prototyping and analyzing it */ -static UA_Double pubWakeupPercentage = 0.6; -static UA_Boolean fileWrite = UA_FALSE; - -/* If the Hardcoded publisher MAC addresses need to be changed, - * change PUBLISHING_MAC_ADDRESS - */ - -/* Set server running as true */ -UA_Boolean running = UA_TRUE; -UA_UInt16 nsIdx = 0; -/* Variables corresponding to PubSub connection creation, - * published data set and writer group */ -UA_NodeId connectionIdent; -UA_NodeId publishedDataSetIdent; -UA_NodeId writerGroupIdent; -/* Variables for counter data handling in address space */ -UA_UInt64 *pubCounterData; -UA_DataValue *pubDataValueRT; -/* Variables for counter data handling in address space */ -UA_Double *pressureData; -UA_DataValue *pressureValueRT; - -/* File to store the data and timestamps for different traffic */ -FILE *fpPublisher; -char *fileName = "publisher_T1.csv"; -/* Array to store published counter data */ -UA_UInt64 publishCounterValue[MAX_MEASUREMENTS]; -UA_Double pressureValues[MAX_MEASUREMENTS]; -size_t measurementsPublisher = 0; -/* Array to store timestamp */ -struct timespec publishTimestamp[MAX_MEASUREMENTS]; - -/* Thread for publisher */ -pthread_t pubthreadID; -struct timespec dataModificationTime; - -/* Thread for user application*/ -pthread_t userApplicationThreadID; - -typedef struct { -UA_Server* ServerRun; -} serverConfigStruct; - -/* Structure to define thread parameters */ -typedef struct { -UA_Server* server; -void* data; -UA_ServerCallback callback; -UA_Duration interval_ms; -UA_UInt64* callbackId; -} threadArg; - -/* Publisher thread routine for ETF */ -void *publisherETF(void *arg); -/* User application thread routine */ -void *userApplicationPub(void *arg); -/* To create multi-threads */ -static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), - char *applicationName, void *serverConfig); - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - running = UA_FALSE; -} - -/** - * **Nanosecond field handling** - * - * Nanosecond field in timespec is checked for overflowing and one second - * is added to seconds field and nanosecond field is set to zero -*/ -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while (timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= SECONDS; - } - -} - -/** - * **Custom callback handling** - * - * Custom callback thread handling overwrites the default timer based - * callback function with the custom (user-specified) callback interval. */ -/* Add a callback for cyclic repetition */ -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - /* Initialize arguments required for the thread to run */ - threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg)); - - /* Pass the value required for the threads */ - threadArguments->server = server; - threadArguments->data = data; - threadArguments->callback = callback; - threadArguments->interval_ms = interval_ms; - threadArguments->callbackId = callbackId; - /* Create the publisher thread with the required priority and core affinity */ - char threadNamePub[10] = "Publisher"; - pubthreadID = threadCreation(pubPriority, pubSubCore, publisherETF, threadNamePub, threadArguments); - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - /* Callback interval need not be modified as it is thread based implementation. - * The thread uses nanosleep for calculating cycle time and modification in - * nanosleep value changes cycle time */ - return UA_STATUSCODE_GOOD; -} - -/* Remove the callback added for cyclic repetition */ -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){ - if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %lu\n", (long unsigned)callbackId); -} - -/** - * **External data source handling** - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -/** - * **PubSub connection handling** - * - * Create a new ConnectionConfig. The addPubSubConnection function takes the - * config and creates a new connection. The Connection identifier is - * copied to the NodeId parameter. - */ -static void -addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){ - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Publisher Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = (UA_UInt32)socketPriority; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); -} - -/** - * **PublishedDataSet handling** - * - * Details about the connection configuration and handling are located - * in the pubsub connection tutorial - */ -static void -addPublishedDataSet(UA_Server *server) { - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); -} - -/** - * **DataSetField handling** - * - * The DataSetField (DSF) is part of the PDS and describes exactly one - * published field. - */ -/* This example only uses two addDataSetField which uses the custom nodes of the XML file - * (pubDataModel.xml) */ -static void -_addDataSetField(UA_Server *server) { - UA_NodeId dataSetFieldIdent; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - pubCounterData = UA_UInt64_new(); - *pubCounterData = 0; - pubDataValueRT = UA_DataValue_new(); - UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]); - pubDataValueRT->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &pubDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - /* If user need to change the nodeid of the custom nodes in the application then it must be - * changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/ - /* The nodeid of the Custom node PublisherCounterVariable is 2005 which is used below */ - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend); - /* setup RT DataSetField config */ - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2005); - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); - UA_NodeId dataSetFieldIdent1; - UA_DataSetFieldConfig dsfConfig1; - memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig)); - pressureData = UA_Double_new(); - *pressureData = 17.07; - pressureValueRT = UA_DataValue_new(); - UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]); - pressureValueRT->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend1; - valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend1.backend.external.value = &pressureValueRT; - valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - /* The nodeid of the Custom node Pressure is 2006 which is used below */ - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1); - /* setup RT DataSetField config */ - dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2006); - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, &dataSetFieldIdent1); - -} - -/** - * **WriterGroup handling** - * - * The WriterGroup (WG) is part of the connection and contains the primary - * configuration parameters for the message creation. - */ -static void -addWriterGroup(UA_Server *server) { - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = cycleTimeMsec; - writerGroupConfig.enabled = UA_FALSE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.writerGroupId = WRITER_GROUP_ID; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - /* The configuration flags for the messages are encapsulated inside the - * message- and transport settings extension objects. These extension - * objects are defined by the standard. e.g. - * UadpWriterGroupMessageDataType */ - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - /* Change message settings of writerGroup to send PublisherId, - * WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader - * of NetworkMessage */ - writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); -} - -/** - * **DataSetWriter handling** - * - * A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is - * linked to exactly one PDS and contains additional information for the - * message generation. - */ -static void -addDataSetWriter(UA_Server *server) { - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); -} - -/** - * **Published data handling** - * - * The published data is updated in the array using this function - */ -static void -updateMeasurementsPublisher(struct timespec start_time, - UA_UInt64 counterValue, UA_Double pressureValue) { - publishTimestamp[measurementsPublisher] = start_time; - publishCounterValue[measurementsPublisher] = counterValue; - pressureValues[measurementsPublisher] = pressureValue; - measurementsPublisher++; -} - -/** - * **Publisher thread routine** - * - * The Publisher thread sleeps for 60% of the cycletime (250us) and prepares the tranmission packet within 40% of - * cycletime. The data published by this thread in one cycle is subscribed by the subscriber thread of pubsub_nodeset_rt_subscriber in the - * next cycle (two cycle timing model). - * - * The publisherETF function is the routine used by the publisher thread. - */ -void *publisherETF(void *arg) { - struct timespec nextnanosleeptime; - UA_ServerCallback pubCallback; - UA_Server* server; - UA_WriterGroup* currentWriterGroup; - UA_UInt64 interval_ns; - UA_UInt64 transmission_time; - - /* Initialise value for nextnanosleeptime timespec */ - nextnanosleeptime.tv_nsec = 0; - - threadArg *threadArgumentsPublisher = (threadArg *)arg; - server = threadArgumentsPublisher->server; - pubCallback = threadArgumentsPublisher->callback; - currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data; - interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS); - - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &nextnanosleeptime); - /* Variable to nano Sleep until 1ms before a 1 second boundary */ - nextnanosleeptime.tv_sec += SECONDS_SLEEP; - nextnanosleeptime.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptime); - - /* Define Ethernet ETF transport settings */ - UA_EthernetWriterGroupTransportDataType ethernettransportSettings; - memset(ðernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType)); - ethernettransportSettings.transmission_time = 0; - - /* Encapsulate ETF config in transportSettings */ - UA_ExtensionObject transportSettings; - memset(&transportSettings, 0, sizeof(UA_ExtensionObject)); - /* TODO: transportSettings encoding and type to be defined */ - transportSettings.content.decoded.data = ðernettransportSettings; - currentWriterGroup->config.transportSettings = transportSettings; - UA_UInt64 roundOffCycleTime = (UA_UInt64)((cycleTimeMsec * MILLI_SECONDS) - (cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS)); - - while (running) { - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL); - transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET; - ethernettransportSettings.transmission_time = transmission_time; - pubCallback(server, currentWriterGroup); - nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns; - nanoSecondFieldConversion(&nextnanosleeptime); - } - - UA_free(threadArgumentsPublisher); - - return (void*)NULL; -} - -/** - * **UserApplication thread routine** - * - * The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model. - * This thread is used to increment the counterdata that will be published by the Publisher thread and also writes the published data in a csv. - */ -void *userApplicationPub(void *arg) { - struct timespec nextnanosleeptimeUserApplication; - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication); - /* Variable to nano Sleep until 1ms before a 1 second boundary */ - nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP; - nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - *pubCounterData = 0; - while (running) { - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); - *pubCounterData = *pubCounterData + 1; - *pressureData = *pressureData + 1; - clock_gettime(CLOCKID, &dataModificationTime); - if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE)) - updateMeasurementsPublisher(dataModificationTime, *pubCounterData, *pressureData); - nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - } - - return (void*)NULL; -} - -/** - * **Thread creation** - * - * The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly - * created thread. - */ -static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), char *applicationName, void *serverConfig){ - - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n"); - exit(1); - } - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\ - "\npthread_setschedparam:%s Thread priority is %d \n", \ - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET((size_t)coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if (errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); - } - - if (CPU_ISSET((size_t)coreAffinity, &cpuset)) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity); - } - - return threadID; - -} - -/** - * **Usage function** - * - * The usage function gives the list of options that can be configured in the application. - * - * ./bin/examples/pubsub_nodeset_rt_publisher -h gives the list of options for running the application. - */ -static void usage(char *appname) -{ - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -i [name] use network interface 'name'\n" - " -C [num] cycle time in milli seconds (default %lf)\n" - " -p Do you need to print the data in console output\n" - " -s [num] set SO_PRIORITY to 'num' (default %d)\n" - " -P [num] Publisher priority value (default %d)\n" - " -U [num] User application priority value (default %d)\n" - " -c [num] run on CPU for publisher'num'(default %d)\n" - " -u [num] run on CPU for userApplication'num'(default %d)\n" - " -t do not use SO_TXTIME\n" - " -m [mac_addr] ToDO:dst MAC address\n" - " -h prints this message and exits\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \ - DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE); -} - -/** - * **Main Server code** - * - * The main function contains publisher threads running - */ -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - char *interface = NULL; - char *progname; - UA_Int32 argInputs = -1; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - pthread_t userThreadID; - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); - - /* Files namespace_example_publisher_generated.h and namespace_example_publisher_generated.c are created from - * pubDataModel.xml in the /src_generated directory by CMake */ - /* Loading the user created variables into the information model from the generated .c and .h files */ - if(namespace_example_publisher_generated(server) != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. " - "Check previous output for any error."); - } - else - { - nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/"); - } - - UA_NetworkAddressUrlDataType networkAddressUrlPub; - - /* Process the command line arguments */ - /* For more information run ./bin/examples/pubsub_nodeset_rt_publisher -h */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) { - switch (argInputs) { - case 'i': - interface = optarg; - break; - case 'C': - cycleTimeMsec = atof(optarg); - break; - case 'f': - fileName = optarg; - fileWrite = UA_TRUE; - fpPublisher = fopen(fileName, "w"); - break; - case 'p': - consolePrint = UA_TRUE; - break; - case 's': - socketPriority = atoi(optarg); - break; - case 'P': - pubPriority = atoi(optarg); - break; - case 'U': - userAppPriority = atoi(optarg); - break; - case 'c': - pubSubCore = atoi(optarg); - break; - case 'u': - userAppCore = atoi(optarg); - break; - case 't': - useSoTxtime = UA_FALSE; - break; - case 'm': - /*ToDo:Need to handle for mac address*/ - break; - case 'h': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if (cycleTimeMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec); - usage(progname); - return -1; - } - - if (!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run"); - usage(progname); - UA_Server_delete(server); - return 0; - } - - networkAddressUrlPub.networkInterface = UA_STRING(interface); - networkAddressUrlPub.url = UA_STRING(PUBLISHING_MAC_ADDRESS); - - addPubSubConnection(server, &networkAddressUrlPub); - addPublishedDataSet(server); - _addDataSetField(server); - addWriterGroup(server); - addDataSetWriter(server); - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); - - serverConfigStruct *serverConfig; - serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct)); - serverConfig->ServerRun = server; - char threadNameUserApplication[22] = "UserApplicationPub"; - userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationPub, threadNameUserApplication, serverConfig); - retval |= UA_Server_run(server, &running); - returnValue = pthread_join(pubthreadID, NULL); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for publisher thread:%d\n", returnValue); - } - returnValue = pthread_join(userThreadID, NULL); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue); - } - - if (fileWrite == UA_TRUE) { - /* Write the published data in a file */ - size_t pubLoopVariable = 0; - for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n", - (long unsigned)publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec, - pressureValues[pubLoopVariable]); - } - fclose(fpPublisher); - } - if (consolePrint == UA_TRUE) { - size_t pubLoopVariable = 0; - for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - printf("%lu,%ld.%09ld,%lf\n", - (long unsigned)publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec, - pressureValues[pubLoopVariable]); - } - } - - UA_Server_delete(server); - UA_free(serverConfig); - UA_free(pubCounterData); - /* Free external data source */ - UA_free(pubDataValueRT); - UA_free(pressureData); - /* Free external data source */ - UA_free(pressureValueRT); - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c b/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c deleted file mode 100644 index 4bd2b0c70..000000000 --- a/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c +++ /dev/null @@ -1,656 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-nodeset-subscriber-tutorial: - * - * Subscriber Realtime example using custom nodes - * ---------------------------------------------- - * - * The purpose of this example file is to use the custom nodes of the XML - * file(subDataModel.xml) for subscriber. This Subscriber example uses the two - * custom nodes (SubscriberCounterVariable and Pressure) created using the XML - * file(subDataModel.xml) for subscribing the packet. The subDataModel.csv will - * contain the nodeids of custom nodes(object and variables) and the nodeids of - * the custom nodes are harcoded inside the addSubscribedVariables API - * - * This example uses two threads namely the Subscriber and UserApplication. The - * Subscriber thread is used to subscribe to data at every cycle. The - * UserApplication thread serves the functionality of the Control loop, which - * reads the Information Model of the Subscriber and the new counterdata will be - * written in the csv along with received timestamp. - * - * Run steps of the Subscriber application as mentioned below: - * - * ``./bin/examples/pubsub_nodeset_rt_subscriber -i `` - * - * For more information run ``./bin/examples/pubsub_nodeset_rt_subscriber -h``. */ - -#define _GNU_SOURCE - -/* For thread operations */ -#include - -#include -#include -#include -#include - -#include "ua_pubsub.h" -#include "open62541/namespace_example_subscriber_generated.h" - -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; -UA_DataSetReaderConfig readerConfig; - -/* to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_subscriber*/ - -/* Configurable Parameters */ -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define QBV_OFFSET 25 * 1000 -#define DEFAULT_SOCKET_PRIORITY 3 -#define PUBLISHER_ID_SUB 2234 -#define WRITER_GROUP_ID_SUB 101 -#define DATA_SET_WRITER_ID_SUB 62541 -#define SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" -#define PORT_NUMBER 62541 - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000 * 1000 -#define SECONDS 1000 * 1000 * 1000 -#define SECONDS_SLEEP 5 -#define DEFAULT_SUB_SCHED_PRIORITY 81 -#define MAX_MEASUREMENTS 30000000 -#define DEFAULT_PUBSUB_CORE 2 -#define DEFAULT_USER_APP_CORE 3 -#define SECONDS_INCREMENT 1 -#define CLOCKID CLOCK_TAI -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75 - -/* Below mentioned parameters can be provided as input using command line arguments - * If user did not provide the below mentioned parameters as input through command line - * argument then default value will be used */ -static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME; -static UA_Boolean consolePrint = UA_FALSE; -static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY; -static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY; -static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE; -static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE; -/* User application Pub will wakeup at the 30% of cycle time and handles the */ -/* user data write in Information model */ -/* After 60% is left for publisher */ -static UA_Double userAppWakeupPercentage = 0.3; -/* Subscriber will wake up at the start of cycle time and then receives the packet */ -static UA_Double subWakeupPercentage = 0; -static UA_Boolean fileWrite = UA_FALSE; - -/* Set server running as true */ -UA_Boolean running = UA_TRUE; -UA_UInt16 nsIdx = 0; - -UA_UInt64 *subCounterData; -UA_DataValue *subDataValueRT; -UA_Double *pressureData; -UA_DataValue *pressureValueRT; - -/* File to store the data and timestamps for different traffic */ -FILE *fpSubscriber; -char *fileName = "subscriber_T4.csv"; -/* Array to store subscribed counter data */ -UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS]; -UA_Double pressureValues[MAX_MEASUREMENTS]; -size_t measurementsSubscriber = 0; -/* Array to store timestamp */ -struct timespec subscribeTimestamp[MAX_MEASUREMENTS]; - -/* Thread for subscriber */ -pthread_t subthreadID; -/* Variable for PubSub connection creation */ -UA_NodeId connectionIdentSubscriber; -struct timespec dataReceiveTime; - -/* Thread for user application*/ -pthread_t userApplicationThreadID; - -typedef struct { -UA_Server* ServerRun; -} serverConfigStruct; - -/* Structure to define thread parameters */ -typedef struct { -UA_Server* server; -void* data; -UA_ServerCallback callback; -UA_Duration interval_ms; -UA_UInt64* callbackId; -} threadArg; - -/* Subscriber thread routine */ -void *subscriber(void *arg); -/* User application thread routine */ -void *userApplicationSub(void *arg); -/* To create multi-threads */ -static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), - char *applicationName, void *serverConfig); - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - running = UA_FALSE; -} - -/** - * **Nanosecond field handling** - * - * Nanosecond field in timespec is checked for overflowing and one second - * is added to seconds field and nanosecond field is set to zero -*/ -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while (timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= SECONDS; - } - -} - -/** - * **External data source handling** - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -/** - * **Subscriber Connection Creation** - * - * Create Subscriber connection for the Subscriber thread - */ -static void -addPubSubConnectionSubscriber(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Subscriber Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.uint32 = UA_UInt32_random(); - retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber); - if (retval == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!"); -} - -/** - * **ReaderGroup** - * - * ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are - * created within a PubSubConnection and automatically deleted if the connection - * is removed. */ -/* Add ReaderGroup to the created connection */ -static void -addReaderGroup(UA_Server *server) { - if (server == NULL) { - return; - } - - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - - UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, - &readerGroupIdentifier); -} - -/** - * **SubscribedDataSet** - * - * Set SubscribedDataSet type to TargetVariables data type - * Add SubscriberCounter variable to the DataSetReader */ -static void addSubscribedVariables (UA_Server *server) { - if (server == NULL) { - return; - } - - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) - UA_calloc(2, sizeof(UA_FieldTargetVariable)); - - subCounterData = UA_UInt64_new(); - *subCounterData = 0; - subDataValueRT = UA_DataValue_new(); - UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]); - subDataValueRT->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - /* If user need to change the nodeid of the custom nodes in the application then it must be - * changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/ - /* The nodeid of the Custom node SubscriberCounterVariable is 2005 which is used below */ - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend); - UA_FieldTargetDataType_init(&targetVars[0].targetVariable); - targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2005); - - pressureData = UA_Double_new(); - *pressureData = 0; - pressureValueRT = UA_DataValue_new(); - UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]); - pressureValueRT->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend1; - valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend1.backend.external.value = &pressureValueRT; - valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - /* The nodeid of the Custom node Pressure is 2006 which is used below */ - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1); - UA_FieldTargetDataType_init(&targetVars[1].targetVariable); - targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2006); - - /* Set the subscribed data to TargetVariable type */ - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2; -} - -/** - * **DataSetReader** - * - * DataSetReader can receive NetworkMessages with the DataSetMessage - * of interest sent by the Publisher. DataSetReader provides - * the configuration necessary to receive and process DataSetMessages - * on the Subscriber side. DataSetReader must be linked with a - * SubscribedDataSet and be contained within a ReaderGroup. */ -/* Add DataSetReader to the ReaderGroup */ -static void -addDataSetReader(UA_Server *server) { - if (server == NULL) { - return; - } - - memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING("DataSet Reader 1"); - UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB; - readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; - readerConfig.publisherId.data = &publisherIdentifier; - readerConfig.writerGroupId = WRITER_GROUP_ID_SUB; - readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB; - - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING ("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 2; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - - /* Unsigned Integer DataType */ - UA_FieldMetaData_init (&pMetaData->fields[0]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[0].valueRank = -1; /* scalar */ - - /* Double DataType */ - UA_FieldMetaData_init (&pMetaData->fields[1]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_DOUBLE].typeId, - &pMetaData->fields[1].dataType); - pMetaData->fields[1].builtInType = UA_NS0ID_DOUBLE; - pMetaData->fields[1].valueRank = -1; /* scalar */ - - /* Setup Target Variables in DSR config */ - addSubscribedVariables(server); - - /* Setting up Meta data configuration in DataSetReader */ - UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); -} - -/** - * **Subscribed data handling** - * - * The subscribed data is updated in the array using this function Subscribed data handling** - */ -static void -updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_Double pressureValue) { - subscribeTimestamp[measurementsSubscriber] = receive_time; - subscribeCounterValue[measurementsSubscriber] = counterValue; - pressureValues[measurementsSubscriber] = pressureValue; - measurementsSubscriber++; -} - -/** - * **Subscriber thread routine** - * - * Subscriber thread will wakeup during the start of cycle at 250us interval and check if the packets are received. - * The subscriber function is the routine used by the subscriber thread. - */ -void *subscriber(void *arg) { - UA_Server* server; - UA_ReaderGroup* currentReaderGroup; - UA_ServerCallback subCallback; - struct timespec nextnanosleeptimeSub; - - threadArg *threadArgumentsSubscriber = (threadArg *)arg; - server = threadArgumentsSubscriber->server; - subCallback = threadArgumentsSubscriber->callback; - currentReaderGroup = (UA_ReaderGroup *)threadArgumentsSubscriber->data; - - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &nextnanosleeptimeSub); - /* Variable to nano Sleep until 1ms before a 1 second boundary */ - nextnanosleeptimeSub.tv_sec += SECONDS_SLEEP; - nextnanosleeptimeSub.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * subWakeupPercentage * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeSub); - while (running) { - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL); - /* Read subscribed data from the SubscriberCounter variable */ - subCallback(server, currentReaderGroup); - nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeSub); - } - - UA_free(threadArgumentsSubscriber); - - return (void*)NULL; -} - -/** - * **UserApplication thread routine** - * - * The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model. - * This thread is used to write the counterdata that is subscribed by the Subscriber thread in a csv. - */ -void *userApplicationSub(void *arg) { - struct timespec nextnanosleeptimeUserApplication; - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication); - /* Variable to nano Sleep until 1ms before a 1 second boundary */ - nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP; - nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - - while (running) { - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); - clock_gettime(CLOCKID, &dataReceiveTime); - if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE)) { - if (*subCounterData > 0) - updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, *pressureData); - } - nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - } - - return (void*)NULL; -} - -/** - * **Thread creation** - * - * The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly - * created thread. - */ -static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), \ - char *applicationName, void *serverConfig){ - - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n"); - exit(1); - } - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\ - "\npthread_setschedparam:%s Thread priority is %d \n", \ - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET((size_t)coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if (errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); - } - - if (CPU_ISSET((size_t)coreAffinity, &cpuset)) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity); - } - - return threadID; - -} - -/** - * **Usage function** - * - * The usage function gives the list of options that can be configured in the application. - * - * ./bin/examples/pubsub_nodeset_rt_subscriber -h gives the list of options for running the application. - */ -static void usage(char *appname) -{ - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -i [name] use network interface 'name'\n" - " -C [num] cycle time in milli seconds (default %lf)\n" - " -p Do you need to print the data in console output\n" - " -P [num] Publisher priority value (default %d)\n" - " -U [num] User application priority value (default %d)\n" - " -c [num] run on CPU for publisher'num'(default %d)\n" - " -u [num] run on CPU for userApplication'num'(default %d)\n" - " -m [mac_addr] ToDO:dst MAC address\n" - " -h prints this message and exits\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SUB_SCHED_PRIORITY, \ - DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE); -} - -/** - * **Main Server code** - * - * The main function contains subscriber threads running - */ -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - char *interface = NULL; - char *progname; - UA_Int32 argInputs = -1; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - pthread_t userThreadID; - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); - - /* Files namespace_example_subscriber_generated.h and namespace_example_subscriber_generated.c are created from - * subDataModel.xml in the /src_generated directory by CMake */ - /* Loading the user created variables into the information model from the generated .c and .h files */ - if(namespace_example_subscriber_generated(server) != UA_STATUSCODE_GOOD) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. " - "Check previous output for any error."); - } - else - { - nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/"); - } - UA_NetworkAddressUrlDataType networkAddressUrlSub; - /* For more information run ./bin/examples/pubsub_nodeset_rt_subscriber -h */ - /* Process the command line arguments */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) { - switch (argInputs) { - case 'i': - interface = optarg; - break; - case 'C': - cycleTimeMsec = atof(optarg); - break; - case 'f': - fileName = optarg; - fileWrite = UA_TRUE; - fpSubscriber = fopen(fileName, "w"); - break; - case 'p': - consolePrint = UA_TRUE; - break; - case 'P': - subPriority = atoi(optarg); - break; - case 'U': - userAppPriority = atoi(optarg); - break; - case 'c': - pubSubCore = atoi(optarg); - break; - case 'u': - userAppCore = atoi(optarg); - break; - case 'm': - /*ToDo:Need to handle for mac address*/ - break; - case 'h': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if (cycleTimeMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec); - usage(progname); - return -1; - } - - if (!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run"); - usage(progname); - UA_Server_delete(server); - return 0; - } - networkAddressUrlSub.networkInterface = UA_STRING(interface); - networkAddressUrlSub.url = UA_STRING(SUBSCRIBING_MAC_ADDRESS); - - addPubSubConnectionSubscriber(server, &networkAddressUrlSub); - addReaderGroup(server); - addDataSetReader(server); - UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier); - UA_Server_enableReaderGroup(server, readerGroupIdentifier); - serverConfigStruct *serverConfig; - serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct)); - serverConfig->ServerRun = server; - - char threadNameUserApplication[22] = "UserApplicationSub"; - userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationSub, threadNameUserApplication, serverConfig); - - retval |= UA_Server_run(server, &running); - - UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); - returnValue = pthread_join(subthreadID, NULL); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for subscriber thread:%d\n", returnValue); - } - returnValue = pthread_join(userThreadID, NULL); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue); - } - if (fileWrite == UA_TRUE) { - /* Write the subscribed data in the file */ - size_t subLoopVariable = 0; - for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n", - (long unsigned)subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec, - pressureValues[subLoopVariable]); - } - fclose(fpSubscriber); - } - if (consolePrint == UA_TRUE) { - size_t subLoopVariable = 0; - for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n", - (long unsigned)subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec, - pressureValues[subLoopVariable]); - } - } - UA_Server_delete(server); - UA_free(serverConfig); - UA_free(subCounterData); - /* Free external data source */ - UA_free(subDataValueRT); - UA_free(pressureData); - /* Free external data source */ - UA_free(pressureValueRT); - return (int)retval; -} - diff --git a/examples/pubsub_realtime/attic/nodeset/subDataModel.csv b/examples/pubsub_realtime/attic/nodeset/subDataModel.csv deleted file mode 100644 index c6d8a50bc..000000000 --- a/examples/pubsub_realtime/attic/nodeset/subDataModel.csv +++ /dev/null @@ -1,4 +0,0 @@ -SubscriberInfo,2001,ObjectType -Subscriber,2004,Object -PublisherCounterValue,2005,Variable -Pressure,2006,Variable diff --git a/examples/pubsub_realtime/attic/nodeset/subDataModel.xml b/examples/pubsub_realtime/attic/nodeset/subDataModel.xml deleted file mode 100644 index bb44cbcd5..000000000 --- a/examples/pubsub_realtime/attic/nodeset/subDataModel.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - http://yourorganisation.org/test/ - - - i=37 - i=9 - i=11 - i=35 - i=40 - i=45 - i=47 - - - SubscriberInfo - SubscriberInfo - - i=58 - ns=1;i=2002 - ns=1;i=2003 - - - - SubscriberCounterVariable - SubscriberCounterVariable - - ns=1;i=2001 - i=63 - i=78 - - - 0 - - - - Pressure - Pressure - - ns=1;i=2001 - i=63 - i=78 - - - 0.0 - - - - Subscriber - SubscriberInfo - - i=85 - ns=1;i=2001 - ns=1;i=2005 - ns=1;i=2006 - - - - SubscriberCounterVariable - SubscriberCounterVariable - - ns=1;i=2004 - i=63 - - - 0 - - - - Pressure - Pressure - - ns=1;i=2004 - i=63 - - - 0.0 - - - diff --git a/examples/pubsub_realtime/attic/pubsub_TSN_loopback.c b/examples/pubsub_realtime/attic/pubsub_TSN_loopback.c deleted file mode 100644 index 943b50971..000000000 --- a/examples/pubsub_realtime/attic/pubsub_TSN_loopback.c +++ /dev/null @@ -1,1742 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-tsn-loopback: - * - * Realtime Loopback Example - * ------------------------- - * - * This tutorial shows publishing and subscribing information in Realtime. This - * example has both Publisher and Subscriber(used as threads, running in same - * core), the Subscriber thread subscribes to the counterdata published by the - * Publisher thread of pubsub_TSN_publisher.c example. The subscribed - * counterdata is again published, which is subscribed by the Subscriber thread - * of pubsub_TSN_publisher.c example. Thus a round-trip of counterdata is - * achieved. The flow of this communication and the trace points are given in - * the diagram below. - * - * Another thread called the UserApplication thread is also used in the example, - * which serves the functionality of the Control loop. In this example, - * UserApplication threads increments the counterData, which is published by the - * Publisher thread and also reads the subscribed data from the Information - * Model and writes the updated counterdata into distinct csv files during each - * cycle. Buffered Network Message will be used for publishing and subscribing - * in the RT path. Further, DataSetField will be accessed via direct pointer - * access between the user interface and the Information Model. - * - * To ensure realtime capabilities, Publisher uses ETF(Earliest Tx-time First) - * to publish information at the calculated tranmission time over Ethernet. - * Subscriber can be used with or without XDP(Xpress Data Processing) over - * Ethernet - * - * Run step of the example is as mentioned below: - * - * ./bin/examples/pubsub_TSN_loopback -interface - * - * For more options, run ./bin/examples/pubsub_TSN_loopback -help - */ - -/* Trace point setup - * - * +--------------+ +----------------+ - * T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4 - * | | Application | ^ | | Application | ^ - * | +--------------+ | | +----------------+ | - * User | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * -----------|--------------|------------------|----------------|-------- - * | | Node 1 | | | | Node 2 | | - * Kernel | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * v +--------------+ | v +----------------+ | - * T2 | TX tcpdump | T7<----------T6 | RX tcpdump | T3 - * | +--------------+ +----------------+ ^ - * | | - * ---------------------------------------------------------- - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -/* For thread operations */ -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "ua_pubsub.h" - -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; - -UA_DataSetReaderConfig readerConfig; - -/*to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_TSN_loopback */ - -/* Configurable Parameters */ -/* These defines enables the publisher and subscriber of the OPCUA stack */ -/* To run only publisher, enable PUBLISHER define alone (comment SUBSCRIBER) */ -#define PUBLISHER -/* To run only subscriber, enable SUBSCRIBER define alone (comment PUBLISHER) */ -#define SUBSCRIBER -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define DEFAULT_QBV_OFFSET 125 -#define DEFAULT_SOCKET_PRIORITY 3 -#define PUBLISHER_ID 2235 -#define WRITER_GROUP_ID 100 -#define DATA_SET_WRITER_ID 62541 -#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3" -#if defined(SUBSCRIBER) -#define PUBLISHER_ID_SUB 2234 -#define WRITER_GROUP_ID_SUB 101 -#define DATA_SET_WRITER_ID_SUB 62541 -#define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" -#endif -#define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes -#define PORT_NUMBER 62541 -#define DEFAULT_XDP_QUEUE 2 -#define PUBSUB_CONFIG_RT_INFORMATION_MODEL - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000 * 1000 -#define SECONDS 1000 * 1000 * 1000 -#define SECONDS_SLEEP 5 -#if defined(PUBLISHER) -/* Publisher will sleep for 60% of cycle time and then prepares the */ -/* transmission packet within 40% */ -static UA_Double pubWakeupPercentage = 0.6; -#endif -/* Subscriber will wakeup only during start of cycle and check whether */ -/* the packets are received */ -static UA_Double subWakeupPercentage = 0; -/* User application Pub/Sub will wakeup at the 30% of cycle time and handles the */ -/* user data such as read and write in Information model */ -static UA_Double userAppWakeupPercentage = 0.3; -/* Priority of Publisher, subscriber, User application and server are kept */ -/* after some prototyping and analyzing it */ -#define DEFAULT_PUB_SCHED_PRIORITY 78 -#define DEFAULT_SUB_SCHED_PRIORITY 81 -#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75 -#define MAX_MEASUREMENTS 1000000 -#define DEFAULT_PUB_CORE 2 -#define DEFAULT_SUB_CORE 2 -#define DEFAULT_USER_APP_CORE 3 -#define SECONDS_INCREMENT 1 -#ifndef CLOCK_TAI -#define CLOCK_TAI 11 -#endif -#define CLOCKID CLOCK_TAI -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION -#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 -#define UA_AES128CTR_KEY_LENGTH 16 -#define UA_AES128CTR_KEYNONCE_LENGTH 4 - -#if defined(PUBLISHER) -UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; -UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; -UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; -#endif - -#if defined(SUBSCRIBER) -UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; -UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0}; -UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; -#endif -#endif - -/* If the Hardcoded publisher/subscriber MAC addresses need to be changed, - * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS - */ - -/* Set server running as true */ -UA_Boolean runningServer = true; - -char* pubMacAddress = DEFAULT_PUBLISHING_MAC_ADDRESS; -char* subMacAddress = DEFAULT_SUBSCRIBING_MAC_ADDRESS; -static UA_Double cycleTimeInMsec = DEFAULT_CYCLE_TIME; -static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY; -static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY; -static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY; -static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY; -static UA_Int32 pubCore = DEFAULT_PUB_CORE; -static UA_Int32 subCore = DEFAULT_SUB_CORE; -static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE; -static UA_Int32 qbvOffset = DEFAULT_QBV_OFFSET; -static UA_UInt32 xdpQueue = DEFAULT_XDP_QUEUE; -static UA_UInt32 xdpFlag = XDP_FLAGS_SKB_MODE; -static UA_UInt32 xdpBindFlag = XDP_COPY; -static UA_Boolean disableSoTxtime = true; -static UA_Boolean enableCsvLog = false; -static UA_Boolean consolePrint = false; -static UA_Boolean signalTerm = false; -static UA_Boolean enableXdpSubscribe = false; - -/* Variables corresponding to PubSub connection creation, - * published data set and writer group */ -UA_NodeId connectionIdent; -UA_NodeId publishedDataSetIdent; -UA_NodeId writerGroupIdent; -UA_NodeId pubNodeID; -UA_NodeId subNodeID; -UA_NodeId pubRepeatedCountNodeID; -UA_NodeId subRepeatedCountNodeID; -UA_NodeId runningPubStatusNodeID; -UA_NodeId runningSubStatusNodeID; -/* Variables for counter data handling in address space */ -UA_UInt64 *pubCounterData = NULL; -UA_DataValue *pubDataValueRT = NULL; -UA_Boolean *runningPub = NULL; -UA_DataValue *runningPubDataValueRT = NULL; -UA_UInt64 *repeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *repeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -UA_UInt64 *subCounterData = NULL; -UA_DataValue *subDataValueRT = NULL; -UA_Boolean *runningSub = NULL; -UA_DataValue *runningSubDataValueRT = NULL; -UA_UInt64 *subRepeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *subRepeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -/** - * CSV file handling - * ~~~~~~~~~~~~~~~~~ - * - * CSV files are written for Publisher and Subscriber thread. csv files include - * the counterdata that is being either Published or Subscribed along with the - * timestamp. These csv files can be used to compute latency for following - * combinations of Tracepoints, T1-T4 and T1-T8. - * - * T1-T8 - Gives the Round-trip time of a counterdata, as the value published by - * the Publisher thread in pubsub_TSN_publisher.c example is subscribed by the - * Subscriber thread in pubsub_TSN_loopback.c example and is published back to - * the pubsub_TSN_publisher.c example */ - -#if defined(PUBLISHER) -/* File to store the data and timestamps for different traffic */ -FILE *fpPublisher; -char *filePublishedData = "publisher_T5.csv"; -/* Array to store published counter data */ -UA_UInt64 publishCounterValue[MAX_MEASUREMENTS]; -size_t measurementsPublisher = 0; -/* Array to store timestamp */ -struct timespec publishTimestamp[MAX_MEASUREMENTS]; -/* Thread for publisher */ -pthread_t pubthreadID; -struct timespec dataModificationTime; -#endif - -#if defined(SUBSCRIBER) -/* File to store the data and timestamps for different traffic */ -FILE *fpSubscriber; -char *fileSubscribedData = "subscriber_T4.csv"; -/* Array to store subscribed counter data */ -UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS]; -size_t measurementsSubscriber = 0; -/* Array to store timestamp */ -struct timespec subscribeTimestamp[MAX_MEASUREMENTS]; -/* Thread for subscriber */ -pthread_t subthreadID; -/* Variable for PubSub connection creation */ -UA_NodeId connectionIdentSubscriber; -struct timespec dataReceiveTime; -#endif - -/* Thread for user application*/ -pthread_t userApplicationThreadID; - -/* Base time handling for the threads */ -struct timespec threadBaseTime; -UA_Boolean baseTimeCalculated = false; - -typedef struct { - UA_Server *ServerRun; -} serverConfigStruct; - -/* Structure to define thread parameters */ -typedef struct { - UA_Server *server; - void *data; - UA_ServerCallback callback; - UA_Duration interval_ms; - UA_UInt64 *callbackId; -} threadArg; - -/** - * Function calls for different threads */ - -/* Publisher thread routine for ETF */ -void *publisherETF(void *arg); -/* Subscriber thread routine */ -void *subscriber(void *arg); -/* User application thread routine */ -void *userApplicationPubSub(void *arg); -/* For adding nodes in the server information model */ -static void addServerNodes(UA_Server *server); -/* For deleting the nodes created */ -static void removeServerNodes(UA_Server *server); -/* To create multi-threads */ -static pthread_t -threadCreation(UA_Int16 threadPriority, size_t coreAffinity, - void *(*thread)(void *), - char *applicationName, void *serverConfig); - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - signalTerm = true; -} - -/** - * Nanosecond field handling - * ~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Nanosecond field in timespec is checked for overflowing and one second is - * added to seconds field and nanosecond field is set to zero. */ - -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while(timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= SECONDS; - } - -} - -/** - * Custom callback handling - * ~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Custom callback thread handling overwrites the default timer based callback - * function with the custom (user-specified) callback interval. */ - -/* Add a callback for cyclic repetition */ -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - /* Initialize arguments required for the thread to run */ - threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg)); - - /* Pass the value required for the threads */ - threadArguments->server = server; - threadArguments->data = data; - threadArguments->callback = callback; - threadArguments->interval_ms = interval_ms; - threadArguments->callbackId = callbackId; - - /* Check the writer group identifier and create the thread accordingly */ - if(UA_NodeId_equal(&identifier, &writerGroupIdent)) { -#if defined(PUBLISHER) - /* Create the publisher thread with the required priority and core affinity */ - char threadNamePub[10] = "Publisher"; - *callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, - publisherETF, threadNamePub, threadArguments); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Publisher thread callback Id: %lu\n", (long unsigned)*callbackId); -#endif - } - else { -#if defined(SUBSCRIBER) - /* Create the subscriber thread with the required priority and core affinity */ - char threadNameSub[11] = "Subscriber"; - *callbackId = threadCreation((UA_Int16)subPriority, (size_t)subCore, - subscriber, threadNameSub, threadArguments); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Subscriber thread callback Id: %lu\n", (long unsigned)*callbackId); -#endif - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - /* Callback interval need not be modified as it is thread based implementation. - * The thread uses nanosleep for calculating cycle time and modification in - * nanosleep value changes cycle time */ - return UA_STATUSCODE_GOOD; -} - -/* Remove the callback added for cyclic repetition */ -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId) { - if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %lu\n", (long unsigned)callbackId); -} - -/** - * External data source handling - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and - * assure that the write leads not to synchronization issues and race - * conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -/** - * Subscriber - * ~~~~~~~~~~ - * - * Create connection, readergroup, datasetreader, subscribedvariables for the - * Subscriber thread. */ - -#if defined(SUBSCRIBER) -static void -addPubSubConnectionSubscriber(UA_Server *server, - UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Subscriber Connection"); - connectionConfig.enabled = true; - - UA_KeyValuePair connectionOptions[4]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = enableXdpSubscribe; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); - UA_UInt32 flags = xdpFlag; - UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); - UA_UInt32 rxqueue = xdpQueue; - UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); - UA_UInt32 bindflags = xdpBindFlag; - UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 4; - - - UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.uint32 = UA_UInt32_random(); - retval |= UA_Server_addPubSubConnection(server, &connectionConfig, - &connectionIdentSubscriber); - if(retval == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "The PubSub Connection was created successfully!"); -} - -/* Add ReaderGroup to the created connection */ -static void -addReaderGroup(UA_Server *server) { - if(server == NULL) - return; - - UA_ReaderGroupConfig readerGroupConfig; - memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Encryption settings */ - UA_ServerConfig *config = UA_Server_getConfig(server); - readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; -#endif - - UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, - &readerGroupIdentifier); - UA_Server_enableReaderGroup(server, readerGroupIdentifier); - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub}; - // TODO security token not necessary for readergroup (extracted from security-header) - UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); -#endif - -} - -/* Set SubscribedDataSet type to TargetVariables data type - * Add subscribedvariables to the DataSetReader */ -static void addSubscribedVariables(UA_Server *server) { - UA_Int32 iterator = 0; - UA_Int32 iteratorRepeatedCount = 0; - - if(server == NULL) - return; - - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) - UA_calloc((REPEATED_NODECOUNTS + 2), sizeof(UA_FieldTargetVariable)); - if(!targetVars) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "FieldTargetVariable - Bad out of memory"); - return; - } - - runningSub = UA_Boolean_new(); - if(!runningSub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningsub - Bad out of memory"); - return; - } - - *runningSub = true; - runningSubDataValueRT = UA_DataValue_new(); - if(!runningSubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningsubDatavalue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningSubDataValueRT->value, runningSub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningSubDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningSubvalueBackend; - runningSubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningSubvalueBackend.backend.external.value = &runningSubDataValueRT; - runningSubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningSubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)30000), runningSubvalueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - iterator++; - /* For creating Targetvariable */ - for(iterator = 1, iteratorRepeatedCount = 0; - iterator <= REPEATED_NODECOUNTS; - iterator++, iteratorRepeatedCount++) { - subRepeatedCounterData[iteratorRepeatedCount] = UA_UInt64_new(); - if(!subRepeatedCounterData[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeRepeatedCounterData - Bad out of memory"); - return; - } - - *subRepeatedCounterData[iteratorRepeatedCount] = 0; - subRepeatedDataValueRT[iteratorRepeatedCount] = UA_DataValue_new(); - if(!subRepeatedDataValueRT[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&subRepeatedDataValueRT[iteratorRepeatedCount]->value, - subRepeatedCounterData[iteratorRepeatedCount], &UA_TYPES[UA_TYPES_UINT64]); - subRepeatedDataValueRT[iteratorRepeatedCount]->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subRepeatedDataValueRT[iteratorRepeatedCount]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount+50000), valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount + 50000); - } - - subCounterData = UA_UInt64_new(); - if(!subCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeCounterData - Bad out of memory"); - return; - } - - *subCounterData = 0; - subDataValueRT = UA_DataValue_new(); - if(!subDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]); - subDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeID, valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = subNodeID; - - /* Set the subscribed data to TargetVariable type */ - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = REPEATED_NODECOUNTS + 2; -} - -/* Add DataSetReader to the ReaderGroup */ -static void -addDataSetReader(UA_Server *server) { - UA_Int32 iterator = 0; - if(server == NULL) { - return; - } - - memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING("DataSet Reader"); - UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB; - readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; - readerConfig.publisherId.data = &publisherIdentifier; - readerConfig.writerGroupId = WRITER_GROUP_ID_SUB; - readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB; - - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = - &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = - UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = - (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - UA_DataSetMetaDataType_init(pMetaData); - /* Static definition of number of fields size to 1 to create one - * targetVariable */ - pMetaData->fieldsSize = REPEATED_NODECOUNTS + 2; - pMetaData->fields = (UA_FieldMetaData*) - UA_Array_new(pMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* Boolean DataType */ - UA_FieldMetaData_init(&pMetaData->fields[iterator]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_BOOLEAN].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_BOOLEAN; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - iterator++; - for(iterator = 1; iterator <= REPEATED_NODECOUNTS; iterator++) { - UA_FieldMetaData_init(&pMetaData->fields[iterator]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - } - - /* Unsigned Integer DataType */ - UA_FieldMetaData_init(&pMetaData->fields[iterator]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - - /* Setup Target Variables in DSR config */ - addSubscribedVariables(server); - - /* Setting up Meta data configuration in DataSetReader */ - UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); -} - -#endif - -#if defined(PUBLISHER) - -/** - * Publisher - * ~~~~~~~~~ - * - * Create connection, writergroup, datasetwriter and publisheddataset for - * Publisher thread. */ - -static void -addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){ - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Publisher Connection"); - connectionConfig.enabled = true; - UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_Variant_setScalar(&connectionOptions[0].value, &socketPriority, - &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Variant_setScalar(&connectionOptions[1].value, &disableSoTxtime, - &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); -} - -/* PublishedDataset handling */ -static void -addPublishedDataSet(UA_Server *server) { - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, - &publishedDataSetIdent); -} - -/* DataSetField handling */ -static void -_addDataSetField(UA_Server *server) { - /* Add a field to the previous created PublishedDataSet */ - UA_NodeId dataSetFieldIdent1; - UA_DataSetFieldConfig dataSetFieldConfig; -#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS - staticValueSource = UA_DataValue_new(); -#endif - - UA_NodeId dataSetFieldIdentRunning; - UA_DataSetFieldConfig dsfConfigPubStatus; - memset(&dsfConfigPubStatus, 0, sizeof(UA_DataSetFieldConfig)); - - runningPub = UA_Boolean_new(); - if(!runningPub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningPub - Bad out of memory"); - return; - } - - *runningPub = true; - runningPubDataValueRT = UA_DataValue_new(); - if(!runningPubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningPubDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningPubDataValueRT->value, runningPub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningPubDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningPubvalueBackend; - runningPubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningPubvalueBackend.backend.external.value = &runningPubDataValueRT; - runningPubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningPubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)20000), runningPubvalueBackend); - - /* setup RT DataSetField config */ - dsfConfigPubStatus.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfigPubStatus.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfigPubStatus, &dataSetFieldIdentRunning); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - - repeatedCounterData[iterator] = UA_UInt64_new(); - if(!repeatedCounterData[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "PublishRepeatedCounter - Bad out of memory"); - return; - } - - *repeatedCounterData[iterator] = 0; - repeatedDataValueRT[iterator] = UA_DataValue_new(); - if(!repeatedDataValueRT[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "PublishRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&repeatedDataValueRT[iterator]->value, repeatedCounterData[iterator], - &UA_TYPES[UA_TYPES_UINT64]); - repeatedDataValueRT[iterator]->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[iterator]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000), valueBackend); - - /* setup RT DataSetField config */ - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = true; - dataSetFieldConfig.field.variable.publishParameters. - publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent1); - } - - UA_NodeId dataSetFieldIdent; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - pubCounterData = UA_UInt64_new(); - if(!pubCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "PublishCounter - Bad out of memory"); - return; - } - - *pubCounterData = 0; - pubDataValueRT = UA_DataValue_new(); - if(!pubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "PublishDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, - &UA_TYPES[UA_TYPES_UINT64]); - pubDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &pubDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, pubNodeID, valueBackend); - - /* setup RT DataSetField config */ - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfig.field.variable.publishParameters.publishedVariable = pubNodeID; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); -} - -/* WriterGroup handling */ -static void -addWriterGroup(UA_Server *server) { - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = cycleTimeInMsec; - writerGroupConfig.enabled = false; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.writerGroupId = WRITER_GROUP_ID; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - UA_ServerConfig *config = UA_Server_getConfig(server); - writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1]; -#endif - /* The configuration flags for the messages are encapsulated inside the - * message- and transport settings extension objects. These extension - * objects are defined by the standard. e.g. - * UadpWriterGroupMessageDataType */ - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - /* Change message settings of writerGroup to send PublisherId, - * WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader - * of NetworkMessage */ - writerGroupMessage->networkMessageContentMask = - (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; - UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); -#endif -} - -/* DataSetWriter handling */ -static void -addDataSetWriter(UA_Server *server) { - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); -} -#endif - -/** - * Published data handling - * ~~~~~~~~~~~~~~~~~~~~~~~ - * - * The published data is updated in the array using this function. */ - -#if defined(PUBLISHER) -static void -updateMeasurementsPublisher(struct timespec start_time, - UA_UInt64 counterValue) { - if(measurementsPublisher >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Publisher: Maximum log measurements reached - Closing the application"); - signalTerm = true; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Pub:%lu,%ld.%09ld\n", - (long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec); - - if(signalTerm != true){ - publishTimestamp[measurementsPublisher] = start_time; - publishCounterValue[measurementsPublisher] = counterValue; - measurementsPublisher++; - } -} -#endif - -#if defined(SUBSCRIBER) - -/** - * Subscribed data handling - * ~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The subscribed data is updated in the array using this function Subscribed - * data handling. */ - -static void -updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue) { - if(measurementsSubscriber >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Subscriber: Maximum log measurements reached - Closing the application"); - signalTerm = true; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Sub:%lu,%ld.%09ld\n", - (long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec); - - if(signalTerm != true){ - subscribeTimestamp[measurementsSubscriber] = receive_time; - subscribeCounterValue[measurementsSubscriber] = counterValue; - measurementsSubscriber++; - } -} -#endif - -#if defined(PUBLISHER) -/** - * Publisher thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This is the Publisher thread that sleeps for 60% of the cycletime (250us) and - * prepares the tranmission packet within 40% of cycletime. The priority of this - * thread is lower than the priority of the Subscriber thread, so the subscriber - * thread executes first during every cycle. The data published by this thread - * in one cycle is subscribed by the subscriber thread of pubsub_TSN_loopback in - * the next cycle (two cycle timing model). - * - * The publisherETF function is the routine used by the publisher thread. */ - -void * -publisherETF(void *arg) { - struct timespec nextnanosleeptime; - UA_ServerCallback pubCallback; - UA_Server* server; - UA_WriterGroup* currentWriterGroup; // TODO: Remove WriterGroup Usage - UA_UInt64 interval_ns; - UA_UInt64 transmission_time; - - /* Initialise value for nextnanosleeptime timespec */ - nextnanosleeptime.tv_nsec = 0; - threadArg *threadArgumentsPublisher = (threadArg *)arg; - server = threadArgumentsPublisher->server; - pubCallback = threadArgumentsPublisher->callback; - currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data; - interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS); - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptime.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the pubWakeUp percentage */ - nextnanosleeptime.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * pubWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptime); - - /* Define Ethernet ETF transport settings */ - UA_EthernetWriterGroupTransportDataType ethernettransportSettings; - memset(ðernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType)); - ethernettransportSettings.transmission_time = 0; - - /* Encapsulate ETF config in transportSettings */ - UA_ExtensionObject transportSettings; - memset(&transportSettings, 0, sizeof(UA_ExtensionObject)); - /* TODO: transportSettings encoding and type to be defined */ - transportSettings.content.decoded.data = ðernettransportSettings; - currentWriterGroup->config.transportSettings = transportSettings; - UA_UInt64 roundOffCycleTime = (UA_UInt64) - ((cycleTimeInMsec * MILLI_SECONDS) - (cycleTimeInMsec * MILLI_SECONDS * pubWakeupPercentage)); - - while(*runningPub) { - /* The Publisher threads wakes up at the configured publisher wake up - * percentage (60%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL); - /* Whenever Ctrl + C pressed, publish running boolean as false to stop - * the subscriber before terminating the application */ - if(signalTerm == true) - *runningPub = false; - - /* Calculation of transmission time using the configured qbv offset by - * the user - Will be handled by publishingOffset in the future */ - transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + - (UA_UInt64)nextnanosleeptime.tv_nsec) + - roundOffCycleTime + (UA_UInt64)(qbvOffset * 1000); - ethernettransportSettings.transmission_time = transmission_time; - /* Publish the data using the pubcallback - UA_WriterGroup_publishCallback(). - * Start publishing when pubCounterData is greater than 1. */ - if(*pubCounterData > 0) - pubCallback(server, currentWriterGroup); - - /* Calculation of the next wake up time by adding the interval with the - * previous wake up time */ - nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns; - nanoSecondFieldConversion(&nextnanosleeptime); - } - - UA_free(threadArgumentsPublisher); - sleep(1); - runningServer = false; - return NULL; -} -#endif - -#if defined(SUBSCRIBER) - -/** - * Subscriber thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This Subscriber thread will wakeup during the start of cycle at 250us - * interval and check if the packets are received. Subscriber thread has the - * highest priority. This Subscriber thread subscribes to the data published by - * the Publisher thread of pubsub_TSN_loopback in the previous cycle. The - * subscriber function is the routine used by the subscriber thread. */ - -void *subscriber(void *arg) { - UA_Server* server; - void* currentReaderGroup; - UA_ServerCallback subCallback; - struct timespec nextnanosleeptimeSub; - UA_UInt64 subInterval_ns; - - threadArg *threadArgumentsSubscriber = (threadArg *)arg; - server = threadArgumentsSubscriber->server; - subCallback = threadArgumentsSubscriber->callback; - currentReaderGroup = threadArgumentsSubscriber->data; - subInterval_ns = (UA_UInt64)(threadArgumentsSubscriber->interval_ms * MILLI_SECONDS); - - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptimeSub.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the subWakeUp percentage */ - nextnanosleeptimeSub.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * subWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptimeSub); - while(*runningSub) { - /* The Subscriber threads wakes up at the configured subscriber wake up - * percentage (0%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL); - /* Receive and process the incoming data */ - subCallback(server, currentReaderGroup); - /* Calculation of the next wake up time by adding the interval with the - * previous wake up time */ - nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)subInterval_ns; - nanoSecondFieldConversion(&nextnanosleeptimeSub); - - /* Whenever Ctrl + C pressed, modify the runningSub boolean to false to - * end this while loop */ - if(signalTerm == true) - *runningSub = false; - } - - /* While ctrl+c is provided in publisher side then loopback application - * need to be closed by after sending *running=0 for subscriber T8 */ - if(*runningSub == false) - signalTerm = true; - -#if defined(SUBSCRIBER) && !defined(PUBLISHER) - runningServer = UA_FALSE; -#endif - UA_free(threadArgumentsSubscriber); - return NULL; -} -#endif - -#if defined(PUBLISHER) || defined(SUBSCRIBER) - -/** - * UserApplication thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The userapplication thread will wakeup at 30% of cycle time and handles the - * userdata(read and write in Information Model). This thread serves the purpose - * of a Control loop, which is used to increment the counterdata to be published - * by the Publisher thread and read the data from Information Model for the - * Subscriber thread and writes the updated counterdata in distinct csv files - * for both threads. */ - -void *userApplicationPubSub(void *arg) { - struct timespec nextnanosleeptimeUserApplication; - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptimeUserApplication.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the userAppWakeUp percentage */ - nextnanosleeptimeUserApplication.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * userAppWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - -#if defined(PUBLISHER) && defined(SUBSCRIBER) - while (*runningSub || *runningPub) { -#else - while (*runningSub) { -#endif - /* The User application threads wakes up at the configured userApp wake - * up percentage (30%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); -#if defined(SUBSCRIBER) - /* Get the time - T4, time where subscribed varibles are read from the - * Information model. At this point, the packet will be already - * subscribed and written into the Information model. As this - * application uses FPM, we do not require explicit call of - * UA_Server_read() to read the subscribed value from the Information - * model. Hence, we take subscribed T4 time here */ - clock_gettime(CLOCKID, &dataReceiveTime); -#endif - -#if defined(PUBLISHER) - /* Pass the received subscribed values to publish variables - * subCounterData value to pubCounter data repeatedSubCounter data - * values to repeatedPubCounter data */ - *pubCounterData = *subCounterData; - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - *repeatedCounterData[iterator] = *subRepeatedCounterData[iterator]; - - /* Get the time - T5, time where the values of the subsribed data were - * copied to the publisher counter variables */ - clock_gettime(CLOCKID, &dataModificationTime); -#endif - - /* Update the T4, T5 time with the counter data in the user defined - * publisher and subscriber arrays */ - if(enableCsvLog || consolePrint) { -#if defined(SUBSCRIBER) - if(*subCounterData > 0) - updateMeasurementsSubscriber(dataReceiveTime, *subCounterData); -#endif - -#if defined(PUBLISHER) - if(*pubCounterData > 0) - updateMeasurementsPublisher(dataModificationTime, *pubCounterData); -#endif - } - - /* Calculation of the next wake up time by adding the interval with the - * previous wake up time */ - nextnanosleeptimeUserApplication.tv_nsec += - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - } - - return NULL; -} -#endif - -/** - * Thread creation - * ~~~~~~~~~~~~~~~ - * - * The threadcreation functionality creates thread with given threadpriority, - * coreaffinity. The function returns the threadID of the newly created thread. */ - -static pthread_t -threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread)(void *), - char *applicationName, void *serverConfig) { - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if(returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "pthread_setschedparam: failed\n"); - exit(1); - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "\npthread_setschedparam:%s Thread priority is %d \n", - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET(coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if(errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if(returnValue != 0) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - ":%s Cannot create thread\n", applicationName); - - if(CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "%s CPU CORE: %lu\n", applicationName, (long unsigned)coreAffinity); - - return threadID; -} - -/** - * Creation of nodes - * ~~~~~~~~~~~~~~~~~~ - * - * The addServerNodes function is used to create the publisher and subscriber - * nodes. */ - -static void addServerNodes(UA_Server *server) { - UA_NodeId objectId; - UA_NodeId newNodeId; - UA_ObjectAttributes object = UA_ObjectAttributes_default; - object.displayName = UA_LOCALIZEDTEXT("en-US", "Counter Object"); - UA_Server_addObjectNode(server, UA_NODEID_NULL, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Counter Object"), UA_NODEID_NULL, - object, NULL, &objectId); - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - UA_UInt64 publishValue = 0; - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter"); - newNodeId = UA_NODEID_STRING(1, "PublisherCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher Counter"), - UA_NODEID_NULL, publisherAttr, NULL, &pubNodeID); - UA_VariableAttributes subscriberAttr = UA_VariableAttributes_default; - UA_UInt64 subscribeValue = 0; - subscriberAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - subscriberAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&subscriberAttr.value, &subscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - subscriberAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber Counter"); - newNodeId = UA_NODEID_STRING(1, "SubscriberCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber Counter"), - UA_NODEID_NULL, subscriberAttr, NULL, &subNodeID); - - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_VariableAttributes repeatedNodePub = UA_VariableAttributes_default; - UA_UInt64 repeatedPublishValue = 0; - repeatedNodePub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodePub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&repeatedNodePub.value, &repeatedPublishValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodePub.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher RepeatedCounter"), - UA_NODEID_NULL, repeatedNodePub, NULL, &pubRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusPub = UA_VariableAttributes_default; - UA_Boolean runningPubStatus = 0; - runningStatusPub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusPub.value, &runningPubStatus, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusPub.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Pub"); - runningStatusPub.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Pub"), - UA_NODEID_NULL, runningStatusPub, NULL, &runningPubStatusNodeID); - - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_VariableAttributes repeatedNodeSub = UA_VariableAttributes_default; - UA_DateTime repeatedSubscribeValue; - UA_Variant_setScalar(&repeatedNodeSub.value, &repeatedSubscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodeSub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodeSub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - repeatedNodeSub.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+50000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber RepeatedCounter"), - UA_NODEID_NULL, repeatedNodeSub, NULL, &subRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusSubscriber = UA_VariableAttributes_default; - UA_Boolean runningSubStatusValue = 0; - runningStatusSubscriber.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusSubscriber.value, &runningSubStatusValue, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusSubscriber.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Sub"); - runningStatusSubscriber.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Sub"), - UA_NODEID_NULL, runningStatusSubscriber, NULL, &runningSubStatusNodeID); -} - -/** - * Deletion of nodes - * ~~~~~~~~~~~~~~~~~ - * - * The removeServerNodes function is used to delete the publisher and subscriber - * nodes. */ - -static void removeServerNodes(UA_Server *server) { - /* Delete the Publisher Counter Node*/ - UA_Server_deleteNode(server, pubNodeID, true); - UA_NodeId_clear(&pubNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_Server_deleteNode(server, pubRepeatedCountNodeID, true); - UA_NodeId_clear(&pubRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningPubStatusNodeID, true); - UA_NodeId_clear(&runningPubStatusNodeID); - UA_Server_deleteNode(server, subNodeID, true); - UA_NodeId_clear(&subNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_Server_deleteNode(server, subRepeatedCountNodeID, true); - UA_NodeId_clear(&subRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningSubStatusNodeID, true); - UA_NodeId_clear(&runningSubStatusNodeID); -} - -/** - * Usage function - * ~~~~~~~~~~~~~~ - * - * The usage function gives the information to run the application. - * - * ``./bin/examples/pubsub_TSN_loopback -interface runs the application.`` - * - * For more options, use ./bin/examples/pubsub_TSN_loopback -h. */ - -static void usage(char *appname) { - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -interface [name] Use network interface 'name'\n" - " -cycleTimeInMsec [num] Cycle time in milli seconds (default %lf)\n" - " -socketPriority [num] Set publisher SO_PRIORITY to (default %d)\n" - " -pubPriority [num] Publisher thread priority value (default %d)\n" - " -subPriority [num] Subscriber thread priority value (default %d)\n" - " -userAppPriority [num] User application thread priority value (default %d)\n" - " -pubCore [num] Run on CPU for publisher (default %d)\n" - " -subCore [num] Run on CPU for subscriber (default %d)\n" - " -userAppCore [num] Run on CPU for userApplication (default %d)\n" - " -pubMacAddress [name] Publisher Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -subMacAddress [name] Subscriber Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -qbvOffset [num] QBV offset value (default %d)\n" - " -disableSoTxtime Do not use SO_TXTIME\n" - " -enableCsvLog Experimental: To log the data in csv files. Support up to 1 million samples\n" - " -enableconsolePrint Experimental: To print the data in console output. Support for higher cycle time\n" - " -enableXdpSubscribe Enable XDP feature for subscriber. XDP_COPY and XDP_FLAGS_SKB_MODE is used by default. Not recommended to be enabled along with blocking socket.\n" - " -xdpQueue [num] XDP queue value (default %d)\n" - " -xdpFlagDrvMode Use XDP in DRV mode\n" - " -xdpBindFlagZeroCopy Use Zero-Copy mode in XDP\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \ - DEFAULT_SUB_SCHED_PRIORITY, DEFAULT_USERAPPLICATION_SCHED_PRIORITY, \ - DEFAULT_PUB_CORE, DEFAULT_SUB_CORE, DEFAULT_USER_APP_CORE, \ - DEFAULT_PUBLISHING_MAC_ADDRESS, DEFAULT_SUBSCRIBING_MAC_ADDRESS, DEFAULT_QBV_OFFSET, DEFAULT_XDP_QUEUE); -} - -/** - * Main Server - * ~~~~~~~~~~~ - * - * The main function contains publisher and subscriber threads running in - * parallel. */ - -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - char *interface = NULL; - UA_Int32 argInputs = 0; - UA_Int32 long_index = 0; - char *progname; - pthread_t userThreadID; - - /* Process the command line arguments */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - - static struct option long_options[] = { - {"interface", required_argument, 0, 'a'}, - {"cycleTimeInMsec", required_argument, 0, 'b'}, - {"socketPriority", required_argument, 0, 'c'}, - {"pubPriority", required_argument, 0, 'd'}, - {"subPriority", required_argument, 0, 'e'}, - {"userAppPriority", required_argument, 0, 'f'}, - {"pubCore", required_argument, 0, 'g'}, - {"subCore", required_argument, 0, 'h'}, - {"userAppCore", required_argument, 0, 'i'}, - {"pubMacAddress", required_argument, 0, 'j'}, - {"subMacAddress", required_argument, 0, 'k'}, - {"qbvOffset", required_argument, 0, 'l'}, - {"disableSoTxtime", no_argument, 0, 'm'}, - {"enableCsvLog", no_argument, 0, 'n'}, - {"enableconsolePrint", no_argument, 0, 'o'}, - {"xdpQueue", required_argument, 0, 'q'}, - {"xdpFlagDrvMode", no_argument, 0, 'r'}, - {"xdpBindFlagZeroCopy", no_argument, 0, 's'}, - {"enableXdpSubscribe", no_argument, 0, 't'}, - {"help", no_argument, 0, 'u'}, - {0, 0, 0, 0 } - }; - - while((argInputs = getopt_long_only(argc, argv,"", long_options, &long_index)) != -1) { - switch(argInputs) { - case 'a': - interface = optarg; - break; - case 'b': - cycleTimeInMsec = atof(optarg); - break; - case 'c': - socketPriority = atoi(optarg); - break; - case 'd': - pubPriority = atoi(optarg); - break; - case 'e': - subPriority = atoi(optarg); - break; - case 'f': - userAppPriority = atoi(optarg); - break; - case 'g': - pubCore = atoi(optarg); - break; - case 'h': - subCore = atoi(optarg); - break; - case 'i': - userAppCore = atoi(optarg); - break; - case 'j': - pubMacAddress = optarg; - break; - case 'k': - subMacAddress = optarg; - break; - case 'l': - qbvOffset = atoi(optarg); - break; - case 'm': - disableSoTxtime = false; - break; - case 'n': - enableCsvLog = true; - break; - case 'o': - consolePrint = true; - break; - case 'q': - xdpQueue = (UA_UInt32)atoi(optarg); - break; - case 'r': - xdpFlag = XDP_FLAGS_DRV_MODE; - break; - case 's': - xdpBindFlag = XDP_ZEROCOPY; - break; - case 't': - enableXdpSubscribe = true; - break; - case 'u': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if(!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Need a network interface to run"); - usage(progname); - UA_Server_delete(server); - return 0; - } - - if(cycleTimeInMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "%f Bad cycle time", cycleTimeInMsec); - usage(progname); - return -1; - } - - if(xdpFlag == XDP_FLAGS_DRV_MODE || xdpBindFlag == XDP_ZEROCOPY) { - if(enableXdpSubscribe == false) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Flag enableXdpSubscribe is false, running application without XDP"); - } - - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION -#if defined(PUBLISHER) && defined(SUBSCRIBER) - /* Instantiate the PubSub SecurityPolicy */ - config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) - UA_calloc(2, sizeof(UA_PubSubSecurityPolicy)); - config->pubSubConfig.securityPoliciesSize = 2; -#else - config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) - UA_malloc(sizeof(UA_PubSubSecurityPolicy)); - config->pubSubConfig.securityPoliciesSize = 1; -#endif -#endif - -#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER) - UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1], - config->logging); -#endif - -#if defined(PUBLISHER) - UA_NetworkAddressUrlDataType networkAddressUrlPub; -#endif - -#if defined(SUBSCRIBER) - UA_NetworkAddressUrlDataType networkAddressUrlSub; -#endif - -#if defined(PUBLISHER) - networkAddressUrlPub.networkInterface = UA_STRING(interface); - networkAddressUrlPub.url = UA_STRING(pubMacAddress); -#endif - -#if defined(SUBSCRIBER) - networkAddressUrlSub.networkInterface = UA_STRING(interface); - networkAddressUrlSub.url = UA_STRING(subMacAddress); -#endif - -#if defined(PUBLISHER) -if(enableCsvLog) - fpPublisher = fopen(filePublishedData, "w"); -#endif - -#if defined(SUBSCRIBER) -if(enableCsvLog) - fpSubscriber = fopen(fileSubscribedData, "w"); -#endif - - /* Server is the new OPCUA model which has both publisher and subscriber - * configuration. Add axis node and OPCUA pubsub client server counter - * nodes. */ - addServerNodes(server); - -#if defined(PUBLISHER) - addPubSubConnection(server, &networkAddressUrlPub); - addPublishedDataSet(server); - _addDataSetField(server); - addWriterGroup(server); - addDataSetWriter(server); - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); -#endif - -#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER) - UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], - config->logging); -#endif - -#if defined(SUBSCRIBER) - addPubSubConnectionSubscriber(server, &networkAddressUrlSub); - addReaderGroup(server); - addDataSetReader(server); - UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif - - serverConfigStruct *serverConfig; - serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct)); - serverConfig->ServerRun = server; -#if defined(PUBLISHER) || defined(SUBSCRIBER) - char threadNameUserAppl[22] = "UserApplicationPubSub"; - userThreadID = threadCreation((UA_Int16)userAppPriority, - (size_t)userAppCore, userApplicationPubSub, - threadNameUserAppl, serverConfig); -#endif - - retval |= UA_Server_run(server, &runningServer); -#if defined(SUBSCRIBER) - UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif - -#if defined(PUBLISHER) || defined(SUBSCRIBER) - returnValue = pthread_join(userThreadID, NULL); - if(returnValue != 0) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "\nPthread Join Failed for User thread:%d\n", returnValue); -#endif - - if(enableCsvLog) { -#if defined(PUBLISHER) - /* Write the published data in the publisher_T1.csv file */ - size_t pubLoopVariable = 0; - for(pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - fprintf(fpPublisher, "%lu,%ld.%09ld\n", - (long unsigned)publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec); - } -#endif -#if defined(SUBSCRIBER) - /* Write the subscribed data in the subscriber_T8.csv file */ - size_t subLoopVariable = 0; - for(subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%lu,%ld.%09ld\n", - (long unsigned)subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec); - } -#endif - } - -#if defined(PUBLISHER) || defined(SUBSCRIBER) - removeServerNodes(server); - UA_Server_delete(server); - UA_free(serverConfig); -#endif - -#if defined(PUBLISHER) - UA_free(runningPub); - UA_free(pubCounterData); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(pubDataValueRT); - UA_free(runningPubDataValueRT); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedDataValueRT[iterator]); - - if(enableCsvLog) - fclose(fpPublisher); -#endif - -#if defined(SUBSCRIBER) - UA_free(runningSub); - UA_free(subCounterData); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(subDataValueRT); - UA_free(runningSubDataValueRT); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedDataValueRT[iterator]); - - if(enableCsvLog) - fclose(fpSubscriber); -#endif - - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c b/examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c deleted file mode 100644 index a262de83a..000000000 --- a/examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c +++ /dev/null @@ -1,1408 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-tutorial: - * - * Realtime Loopback Example - * ------------------------ - * - * This tutorial shows publishing and subscribing information in Realtime. - * This example has both Publisher and Subscriber and userapplication handling in the same thread. This example - * receives the data from the publisher application (pubsub_TSN_publisher_mulltiple thread application) and process the - * received data and send them back to the publisher application - * Another additional feature called the Blocking Socket is employed in the Subscriber thread. When using Blocking Socket, - * the Subscriber thread remains in "blocking mode" until a message is received from every wake up time of the thread. In other words, - * the timeout is overwritten and the thread continuously waits for the message from every wake up time of the thread. - * Once the message is received, the Subscriber thread updates the value in the Information Model, sleeps up to wake up time and - * again waits for the next message. This process is repeated until the application is terminated. - * - * Run step of the example is as mentioned below: - * - * ./bin/examples/pubsub_TSN_loopback_single_thread -interface -operBaseTime -monotonicOffset - * - * For more options, run ./bin/examples/pubsub_TSN_loopback_single_thread -h - */ - -/** - * Trace point setup - * - * +--------------+ +----------------+ - * T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4 - * | | Application | ^ | | Application | ^ - * | +--------------+ | | +----------------+ | - * User | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * -----------|--------------|------------------------|----------------|-------- - * | | Node 1 | | | | Node 2 | | - * Kernel | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * v +--------------+ | v +----------------+ | - * T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3 - * | +--------------+ +----------------+ ^ - * | | - * ---------------------------------------------------------------- - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -/* For thread operations */ -#include - -#include -#include -#include -#include -#include -#include - -#include "ua_pubsub.h" - -/*to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_TSN_loopback_single_thread */ - -/* Configurable Parameters */ -//If you disable the below macro then two way communication then only subscriber will be active -#define TWO_WAY_COMMUNICATION -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define DEFAULT_QBV_OFFSET 125 -#define DEFAULT_SOCKET_PRIORITY 7 -#define PUBLISHER_ID 2235 -#define WRITER_GROUP_ID 100 -#define DATA_SET_WRITER_ID 62541 -#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3" -#define DEFAULT_PUBLISHER_MULTICAST_ADDRESS "opc.udp://224.0.0.32:4840/" -#define PUBLISHER_ID_SUB 2234 -#define WRITER_GROUP_ID_SUB 101 -#define DATA_SET_WRITER_ID_SUB 62541 -#define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" -#define DEFAULT_SUBSCRIBER_MULTICAST_ADDRESS "opc.udp://224.0.0.22:4840/" -#define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes -#define PORT_NUMBER 62541 -#define DEFAULT_PUBSUBAPP_THREAD_PRIORITY 90 -#define DEFAULT_PUBSUBAPP_THREAD_CORE 1 - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000000 -#if defined(__arm__) -#define SECONDS 1e9 -#else -#define SECONDS 1000000000 -#endif -#define SECONDS_SLEEP 5 - -#define MAX_MEASUREMENTS 100000 -#define SECONDS_INCREMENT 1 -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif -#define CLOCKID CLOCK_MONOTONIC -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define UDP_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp" - -/* If the Hardcoded publisher/subscriber MAC addresses need to be changed, - * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS - */ - -/* Set server running as true */ -UA_Boolean runningServer = UA_TRUE; - -char* pubUri = DEFAULT_PUBLISHING_MAC_ADDRESS; -char* subUri = DEFAULT_SUBSCRIBING_MAC_ADDRESS; - -static UA_Double cycleTimeInMsec = DEFAULT_CYCLE_TIME; -static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY; -static UA_Int32 pubSubAppPriority = 90; -static UA_Int32 pubSubAppCore = 1; -static UA_Int32 qbvOffset = DEFAULT_QBV_OFFSET; -static UA_Boolean disableSoTxtime = UA_TRUE; -static UA_Boolean enableCsvLog = UA_FALSE; -static UA_Boolean consolePrint = UA_FALSE; -static UA_Boolean signalTerm = UA_FALSE; - -#ifdef TWO_WAY_COMMUNICATION -/* Variables corresponding to PubSub connection creation, - * published data set and writer group */ -UA_NodeId connectionIdent; -UA_NodeId publishedDataSetIdent; -UA_NodeId writerGroupIdent; -UA_NodeId pubNodeID; -UA_NodeId pubRepeatedCountNodeID; -UA_NodeId runningPubStatusNodeID; -/* Variables for counter data handling in address space */ -UA_UInt64 *pubCounterData = NULL; -UA_DataValue *pubDataValueRT = NULL; -UA_Boolean *runningPub = NULL; -UA_DataValue *runningPubDataValueRT = NULL; -UA_UInt64 *repeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *repeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; -#else -static UA_UInt64 previousSubCounterData = 0; -#endif - -UA_NodeId subNodeID; -UA_NodeId subRepeatedCountNodeID; -UA_NodeId runningSubStatusNodeID; -UA_UInt64 *subCounterData = NULL; -UA_DataValue *subDataValueRT = NULL; -UA_Boolean *runningSub = NULL; -UA_DataValue *runningSubDataValueRT = NULL; -UA_UInt64 *subRepeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *subRepeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -/** - * **CSV file handling** - * - * csv files are written for pubSubApp thread. - * csv files include the counterdata that is being either Published or Subscribed - * along with the timestamp. These csv files can be used to compute latency for following - * combinations of Tracepoints, T1-T4 and T1-T8. - * - * T1-T8 - Gives the Round-trip time of a counterdata, as the value published by the Publisher thread - * in pubsub_TSN_publisher_multiple_thread.c example is subscribed by the pubSubApp thread in pubsub_TSN_loopback_single_thread.c - * example and is published back to the pubsub_TSN_publisher_multiple_thread.c example - */ -#ifdef TWO_WAY_COMMUNICATION -/* File to store the data and timestamps for different traffic */ -FILE *fpPublisher; -char *filePublishedData = "publisher_T5.csv"; -/* Array to store published counter data */ -UA_UInt64 publishCounterValue[MAX_MEASUREMENTS]; -size_t measurementsPublisher = 0; -/* Array to store timestamp */ -struct timespec publishTimestamp[MAX_MEASUREMENTS]; -struct timespec dataModificationTime; -#endif - -/* File to store the data and timestamps for different traffic */ -FILE *fpSubscriber; -char *fileSubscribedData = "subscriber_T4.csv"; -/* Array to store subscribed counter data */ -UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS]; -size_t measurementsSubscriber = 0; -/* Array to store timestamp */ -struct timespec subscribeTimestamp[MAX_MEASUREMENTS]; -/* Variable for PubSub connection creation */ -UA_NodeId connectionIdentSubscriber; -struct timespec dataReceiveTime; -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; -UA_DataSetReaderConfig readerConfig; - -/* Structure to define thread parameters */ -typedef struct { -UA_Server* server; -void* pubData; -void* subData; -UA_ServerCallback pubCallback; -UA_ServerCallback subCallback; -UA_Duration interval_ms; -UA_UInt64 operBaseTime; -UA_UInt64 monotonicOffset; -UA_UInt64 packetLossCount; -} threadArgPubSub; - -threadArgPubSub *threadArgPubSub1; - -/* PubSub application thread routine */ -void *pubSubApp(void *arg); -/* For adding nodes in the server information model */ -static void addServerNodes(UA_Server *server); -/* For deleting the nodes created */ -static void removeServerNodes(UA_Server *server); -/* To create multi-threads */ -static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread) (void *), - char *applicationName, void *serverConfig); -void userApplication(UA_UInt64 monotonicOffsetValue); - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - signalTerm = UA_TRUE; -} - -/** - * **Nanosecond field handling** - * - * Nanosecond field in timespec is checked for overflowing and one second - * is added to seconds field and nanosecond field is set to zero -*/ -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while (timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= (__syscall_slong_t)(SECONDS); - } - -} - -/** - * **Custom callback handling** - * - * Custom callback thread handling overwrites the default timer based - * callback function with the custom (user-specified) callback interval. */ -/* Add a callback for cyclic repetition */ -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { -#ifdef TWO_WAY_COMMUNICATION - /* Check the writer group identifier and create the thread accordingly */ - if(UA_NodeId_equal(&identifier, &writerGroupIdent)) { - threadArgPubSub1->pubData = data; - threadArgPubSub1->pubCallback = callback; - threadArgPubSub1->interval_ms = interval_ms; - } - else { -#endif - threadArgPubSub1->subData = data; - threadArgPubSub1->subCallback = callback; -#ifdef TWO_WAY_COMMUNICATION - } -#else - threadArgPubSub1->interval_ms = interval_ms; -#endif - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - /* Callback interval need not be modified as it is thread based implementation. - * The thread uses nanosleep for calculating cycle time and modification in - * nanosleep value changes cycle time */ - return UA_STATUSCODE_GOOD; -} - - -/* Remove the callback added for cyclic repetition */ -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { -/* ToDo: Handle thread id */ -} - -/** - * **External data source handling** - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -/** - * **Subscriber** - * - * Create connection, readergroup, datasetreader, subscribedvariables for the Subscriber thread. - */ -static void -addPubSubConnectionSubscriber(UA_Server *server, UA_String *transportProfile, - UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Subscriber Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber; - connectionConfig.transportProfileUri = *transportProfile; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.uint32 = UA_UInt32_random(); - retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber); - if (retval == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!"); -} - -/* Add ReaderGroup to the created connection */ -static void -addReaderGroup(UA_Server *server) { - if(server == NULL) - return; - - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - - UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, - &readerGroupIdentifier); -} - -/* Set SubscribedDataSet type to TargetVariables data type - * Add subscribedvariables to the DataSetReader */ -static void addSubscribedVariables (UA_Server *server) { - UA_Int32 iterator = 0; - UA_Int32 iteratorRepeatedCount = 0; - - if(server == NULL) { - return; - } - - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) UA_calloc((REPEATED_NODECOUNTS + 2), sizeof(UA_FieldTargetVariable)); - if(!targetVars) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "FieldTargetVariable - Bad out of memory"); - return; - } - - runningSub = UA_Boolean_new(); - if(!runningSub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningsub - Bad out of memory"); - UA_free(targetVars); - return; - } - - *runningSub = UA_TRUE; - runningSubDataValueRT = UA_DataValue_new(); - if(!runningSubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningsubDatavalue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&runningSubDataValueRT->value, runningSub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningSubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningSubvalueBackend; - runningSubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningSubvalueBackend.backend.external.value = &runningSubDataValueRT; - runningSubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningSubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)30000), runningSubvalueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - iterator++; - /* For creating Targetvariable */ - for (iterator = 1, iteratorRepeatedCount = 0; iterator <= REPEATED_NODECOUNTS; iterator++, iteratorRepeatedCount++) - { - subRepeatedCounterData[iteratorRepeatedCount] = UA_UInt64_new(); - if(!subRepeatedCounterData[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeRepeatedCounterData - Bad out of memory"); - UA_free(targetVars); - return; - } - - *subRepeatedCounterData[iteratorRepeatedCount] = 0; - subRepeatedDataValueRT[iteratorRepeatedCount] = UA_DataValue_new(); - if(!subRepeatedDataValueRT[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeRepeatedCounterDataValue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&subRepeatedDataValueRT[iteratorRepeatedCount]->value, subRepeatedCounterData[iteratorRepeatedCount], &UA_TYPES[UA_TYPES_UINT64]); - subRepeatedDataValueRT[iteratorRepeatedCount]->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subRepeatedDataValueRT[iteratorRepeatedCount]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount+50000), valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount + 50000); - } - - subCounterData = UA_UInt64_new(); - if(!subCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeCounterData - Bad out of memory"); - UA_free(targetVars); - return; - } - - *subCounterData = 0; - subDataValueRT = UA_DataValue_new(); - if(!subDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeDataValue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]); - subDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeID, valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = subNodeID; - - /* Set the subscribed data to TargetVariable type */ - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = REPEATED_NODECOUNTS + 2; -} - -/* Add DataSetReader to the ReaderGroup */ -static void -addDataSetReader(UA_Server *server) { - UA_Int32 iterator = 0; - if(server == NULL) { - return; - } - - memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING("DataSet Reader"); - UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB; - readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; - readerConfig.publisherId.data = &publisherIdentifier; - readerConfig.writerGroupId = WRITER_GROUP_ID_SUB; - readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB; - - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - UA_DataSetMetaDataType_init (pMetaData); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = REPEATED_NODECOUNTS + 2; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* Boolean DataType */ - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_BOOLEAN].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_BOOLEAN; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - iterator++; - for (iterator = 1; iterator <= REPEATED_NODECOUNTS; iterator++) - { - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - } - - /* Unsigned Integer DataType */ - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - - /* Setup Target Variables in DSR config */ - addSubscribedVariables(server); - - /* Setting up Meta data configuration in DataSetReader */ - UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); -} - -#ifdef TWO_WAY_COMMUNICATION -/** - * **Publisher** - * - * Create connection, writergroup, datasetwriter and publisheddataset for Publisher thread. - */ -static void -addPubSubConnection(UA_Server *server, UA_String *transportProfile, - UA_NetworkAddressUrlDataType *networkAddressUrlPub){ - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Publisher Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub; - connectionConfig.transportProfileUri = *transportProfile; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = PUBLISHER_ID; - - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_Variant_setScalar(&connectionOptions[0].value, &socketPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Variant_setScalar(&connectionOptions[1].value, &disableSoTxtime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); -} - -/* PublishedDataset handling */ -static void -addPublishedDataSet(UA_Server *server) { - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); -} - -/* DataSetField handling */ -static void -_addDataSetField(UA_Server *server) { - /* Add a field to the previous created PublishedDataSet */ - UA_NodeId dataSetFieldIdent1; - UA_DataSetFieldConfig dataSetFieldConfig; -#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS - staticValueSource = UA_DataValue_new(); -#endif - - UA_NodeId dataSetFieldIdentRunning; - UA_DataSetFieldConfig dsfConfigPubStatus; - memset(&dsfConfigPubStatus, 0, sizeof(UA_DataSetFieldConfig)); - - runningPub = UA_Boolean_new(); - if(!runningPub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPub - Bad out of memory"); - return; - } - - *runningPub = UA_TRUE; - runningPubDataValueRT = UA_DataValue_new(); - if(!runningPubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPubDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningPubDataValueRT->value, runningPub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningPubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningPubvalueBackend; - runningPubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningPubvalueBackend.backend.external.value = &runningPubDataValueRT; - runningPubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningPubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)20000), runningPubvalueBackend); - - /* setup RT DataSetField config */ - dsfConfigPubStatus.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfigPubStatus.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfigPubStatus, &dataSetFieldIdentRunning); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - - repeatedCounterData[iterator] = UA_UInt64_new(); - if(!repeatedCounterData[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounter - Bad out of memory"); - return; - } - - *repeatedCounterData[iterator] = 0; - repeatedDataValueRT[iterator] = UA_DataValue_new(); - if(!repeatedDataValueRT[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&repeatedDataValueRT[iterator]->value, repeatedCounterData[iterator], &UA_TYPES[UA_TYPES_UINT64]); - repeatedDataValueRT[iterator]->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[iterator]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000), valueBackend); - - /* setup RT DataSetField config */ - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dataSetFieldConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent1); - } - - UA_NodeId dataSetFieldIdent; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - pubCounterData = UA_UInt64_new(); - if(!pubCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishCounter - Bad out of memory"); - return; - } - - *pubCounterData = 0; - pubDataValueRT = UA_DataValue_new(); - if(!pubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]); - pubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &pubDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, pubNodeID, valueBackend); - - /* setup RT DataSetField config */ - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig.field.variable.publishParameters.publishedVariable = pubNodeID; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); -} - -/* WriterGroup handling */ -static void -addWriterGroup(UA_Server *server) { - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = cycleTimeInMsec; - writerGroupConfig.enabled = UA_FALSE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.writerGroupId = WRITER_GROUP_ID; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - /* The configuration flags for the messages are encapsulated inside the - * message- and transport settings extension objects. These extension - * objects are defined by the standard. e.g. - * UadpWriterGroupMessageDataType */ - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - /* Change message settings of writerGroup to send PublisherId, - * WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader - * of NetworkMessage */ - writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); -} - -/* DataSetWriter handling */ -static void -addDataSetWriter(UA_Server *server) { - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); -} - -/** - * **Published data handling** - * - * The published data is updated in the array using this function - */ -static void -updateMeasurementsPublisher(struct timespec start_time, - UA_UInt64 counterValue, UA_UInt64 monotonicOffsetValue) { - if(measurementsPublisher >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Publisher: Maximum log measurements reached - Closing the application"); - signalTerm = UA_TRUE; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%"PRId64",%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec); - - if (signalTerm != UA_TRUE){ - UA_UInt64 actualTimeValue = (UA_UInt64)((start_time.tv_sec * SECONDS) + start_time.tv_nsec) + monotonicOffsetValue; - publishTimestamp[measurementsPublisher].tv_sec = (__time_t)(actualTimeValue/(UA_UInt64)SECONDS); - publishTimestamp[measurementsPublisher].tv_nsec = (__syscall_slong_t)(actualTimeValue%(UA_UInt64)SECONDS); - publishCounterValue[measurementsPublisher] = counterValue; - measurementsPublisher++; - } -} -#endif - -/** - * **Subscribed data handling** - * - * The subscribed data is updated in the array using this function Subscribed data handling** - */ -static void -updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_UInt64 monotonicOffsetValue) { - if(measurementsSubscriber >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Subscriber: Maximum log measurements reached - Closing the application"); - signalTerm = UA_TRUE; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%"PRId64",%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec); - - if (signalTerm != UA_TRUE){ - UA_UInt64 actualTimeValue = (UA_UInt64)((receive_time.tv_sec * SECONDS) + receive_time.tv_nsec) + monotonicOffsetValue; - subscribeTimestamp[measurementsSubscriber].tv_sec = (__time_t)(actualTimeValue/(UA_UInt64)SECONDS); - subscribeTimestamp[measurementsSubscriber].tv_nsec = (__syscall_slong_t)(actualTimeValue%(UA_UInt64)SECONDS); - subscribeCounterValue[measurementsSubscriber] = counterValue; - measurementsSubscriber++; - } -} - -/** - * userApplication function is used to increment the counterdata to be published by the Publisher and - * read the data from Information Model for the Subscriber and writes the updated counterdata in distinct csv files - **/ -void userApplication(UA_UInt64 monotonicOffsetValue) { - - clock_gettime(CLOCKID, &dataReceiveTime); -#ifdef TWO_WAY_COMMUNICATION - /* Check packet loss count */ - /* *subCounterData > 0 check is kept because while setting the writerGroupToOperational condition - * in the pubsub_TSN_publisher_single_thread.c publisher publishes a data of zero once while callback setup */ - if ((*subCounterData > 0) && (*subCounterData != (*pubCounterData + 1))) { - UA_UInt64 missedCount = *subCounterData - (*pubCounterData + 1); - threadArgPubSub1->packetLossCount += missedCount; - } - - *pubCounterData = *subCounterData; - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - *repeatedCounterData[iterator] = *subRepeatedCounterData[iterator]; - - clock_gettime(CLOCKID, &dataModificationTime); -#else - if ((*subCounterData > 0) && (*subCounterData != (previousSubCounterData + 1))) { - UA_UInt64 missedCount = *subCounterData - (previousSubCounterData + 1); - threadArgPubSub1->packetLossCount += missedCount; - } - - previousSubCounterData = *subCounterData; -#endif - - if (enableCsvLog || consolePrint) { - if (*subCounterData > 0) - updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, monotonicOffsetValue); -#ifdef TWO_WAY_COMMUNICATION - if (*pubCounterData > 0) - updateMeasurementsPublisher(dataModificationTime, *pubCounterData, monotonicOffsetValue); -#endif - } - - /* *runningPub variable made false and send to the publisher application which is running in another node - which will close the application during blocking socket condition */ - if (signalTerm == UA_TRUE) { -#ifdef TWO_WAY_COMMUNICATION - *runningPub = UA_FALSE; -#endif - *runningSub = UA_FALSE; - } -} - -/** - * **PubSub thread routine** - */ -void *pubSubApp(void *arg) { - struct timespec nextnanosleeptimePubSubApplication; - //struct timespec currentTimeInTsCheck; - UA_Server* server; - UA_ReaderGroup* currentReaderGroup; - UA_ServerCallback subCallback; -#ifdef TWO_WAY_COMMUNICATION - UA_ServerCallback pubCallback; - UA_WriterGroup* currentWriterGroup; -#endif - UA_UInt64 interval_ms; - UA_UInt64 monotonicOffsetValue = 0; - - server = threadArgPubSub1->server; - currentReaderGroup = (UA_ReaderGroup*)threadArgPubSub1->subData; - subCallback = threadArgPubSub1->subCallback; -#ifdef TWO_WAY_COMMUNICATION - currentWriterGroup = (UA_WriterGroup *)threadArgPubSub1->pubData; - pubCallback = threadArgPubSub1->pubCallback; -#endif - interval_ms = (UA_UInt64)(threadArgPubSub1->interval_ms * MILLI_SECONDS); - - //To synchronize the application along with gating cycle the below calculations are made - //Below calculations are done for monotonic clock - struct timespec currentTimeInTs; - UA_UInt64 addingValueToStartTime; - UA_UInt64 timeToStart; - clock_gettime(CLOCKID, ¤tTimeInTs); - UA_UInt64 currentTimeInNs = (UA_UInt64)((currentTimeInTs.tv_sec * (SECONDS)) + currentTimeInTs.tv_nsec); - currentTimeInNs = currentTimeInNs + threadArgPubSub1->monotonicOffset; - timeToStart = currentTimeInNs + (SECONDS_SLEEP * (UA_UInt64)(SECONDS)); //Adding 5 seconds to start the cycle - - if (threadArgPubSub1->operBaseTime != 0){ - UA_UInt64 moduloValueOfOperBaseTime = timeToStart % threadArgPubSub1->operBaseTime; - if(moduloValueOfOperBaseTime > interval_ms) - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime % interval_ms); - else - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime); - - timeToStart = timeToStart + addingValueToStartTime; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - else{ - timeToStart = timeToStart - timeToStart%interval_ms; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - - UA_UInt64 CycleStartTimeS = (UA_UInt64)(timeToStart / (UA_UInt64)(SECONDS)); - UA_UInt64 CycleStartTimeNs = (UA_UInt64)(timeToStart - (CycleStartTimeS * (UA_UInt64)(SECONDS))); - nextnanosleeptimePubSubApplication.tv_sec = (__time_t )(CycleStartTimeS); - nextnanosleeptimePubSubApplication.tv_nsec = (__syscall_slong_t)(CycleStartTimeNs); - nanoSecondFieldConversion(&nextnanosleeptimePubSubApplication); - monotonicOffsetValue = threadArgPubSub1->monotonicOffset; - - while (*runningSub) { - //Sleep for cycle time - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimePubSubApplication, NULL); - //Call the subscriber callback to receive the data - subCallback(server, currentReaderGroup); - userApplication(monotonicOffsetValue); -#ifdef TWO_WAY_COMMUNICATION - //ToDo:Handled only for without SO_TXTIME - //Call the publish callback to publish the data into the network - pubCallback(server, currentWriterGroup); -#endif - //Calculate nextwakeup time - nextnanosleeptimePubSubApplication.tv_nsec += (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimePubSubApplication); - } - - sleep(1); - runningServer = UA_FALSE; - return (void*)NULL; -} - -/** - * **Thread creation** - * - * The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly - * created thread. - */ - -static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread) (void *), char *applicationName, \ - void *serverConfig){ - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n"); - exit(1); - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\ - "\npthread_setschedparam:%s Thread priority is %d \n", \ - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET(coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if (errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if (returnValue != 0) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); - - if (CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %zu\n", applicationName, coreAffinity); - - return threadID; -} - -/** - * **Creation of nodes** - * - * The addServerNodes function is used to create the publisher and subscriber - * nodes. - */ -static void addServerNodes(UA_Server *server) { - UA_NodeId objectId; - UA_NodeId newNodeId; - UA_ObjectAttributes object = UA_ObjectAttributes_default; - object.displayName = UA_LOCALIZEDTEXT("en-US", "Counter Object"); - UA_Server_addObjectNode(server, UA_NODEID_NULL, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Counter Object"), UA_NODEID_NULL, - object, NULL, &objectId); -#ifdef TWO_WAY_COMMUNICATION - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - UA_UInt64 publishValue = 0; - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter"); - newNodeId = UA_NODEID_STRING(1, "PublisherCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher Counter"), - UA_NODEID_NULL, publisherAttr, NULL, &pubNodeID); -#endif - UA_VariableAttributes subscriberAttr = UA_VariableAttributes_default; - UA_UInt64 subscribeValue = 0; - subscriberAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - subscriberAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&subscriberAttr.value, &subscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - subscriberAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber Counter"); - newNodeId = UA_NODEID_STRING(1, "SubscriberCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber Counter"), - UA_NODEID_NULL, subscriberAttr, NULL, &subNodeID); -#ifdef TWO_WAY_COMMUNICATION - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodePub = UA_VariableAttributes_default; - UA_UInt64 repeatedPublishValue = 0; - repeatedNodePub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodePub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&repeatedNodePub.value, &repeatedPublishValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodePub.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher RepeatedCounter"), - UA_NODEID_NULL, repeatedNodePub, NULL, &pubRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusPub = UA_VariableAttributes_default; - UA_Boolean runningPubStatus = 0; - runningStatusPub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusPub.value, &runningPubStatus, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusPub.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Pub"); - runningStatusPub.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Pub"), - UA_NODEID_NULL, runningStatusPub, NULL, &runningPubStatusNodeID); -#endif - - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodeSub = UA_VariableAttributes_default; - UA_DateTime repeatedSubscribeValue; - UA_Variant_setScalar(&repeatedNodeSub.value, &repeatedSubscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodeSub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodeSub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - repeatedNodeSub.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+50000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber RepeatedCounter"), - UA_NODEID_NULL, repeatedNodeSub, NULL, &subRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusSubscriber = UA_VariableAttributes_default; - UA_Boolean runningSubStatusValue = 0; - runningStatusSubscriber.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusSubscriber.value, &runningSubStatusValue, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusSubscriber.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Sub"); - runningStatusSubscriber.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Sub"), - UA_NODEID_NULL, runningStatusSubscriber, NULL, &runningSubStatusNodeID); -} - -/** - * **Deletion of nodes** - * - * The removeServerNodes function is used to delete the publisher and subscriber - * nodes. - */ - -static void removeServerNodes(UA_Server *server) { - /* Delete the Publisher Counter Node*/ -#ifdef TWO_WAY_COMMUNICATION - UA_Server_deleteNode(server, pubNodeID, UA_TRUE); - UA_NodeId_clear(&pubNodeID); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_Server_deleteNode(server, pubRepeatedCountNodeID, UA_TRUE); - UA_NodeId_clear(&pubRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE); - UA_NodeId_clear(&runningPubStatusNodeID); -#endif - UA_Server_deleteNode(server, subNodeID, UA_TRUE); - UA_NodeId_clear(&subNodeID); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_Server_deleteNode(server, subRepeatedCountNodeID, UA_TRUE); - UA_NodeId_clear(&subRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningSubStatusNodeID, UA_TRUE); - UA_NodeId_clear(&runningSubStatusNodeID); -} - -/** - * **Usage function** - * - * The usage function gives the information to run the application. - * - * ./bin/examples/pubsub_TSN_loopback_single_thread -interface -operBaseTime -monotonicOffset - * - * For more options, use ./bin/examples/pubsub_TSN_loopback_single_thread -h. - */ - -static void usage(char *appname) -{ - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -interface [name] Use network interface 'name'\n" - " -cycleTimeInMsec [num] Cycle time in milli seconds (default %lf)\n" - " -socketPriority [num] Set publisher SO_PRIORITY to (default %d)\n" - " -pubSubAppPriority [num] pubSubApp thread priority value (default %d)\n" - " -pubSubAppCore [num] Run on CPU for userApplication (default %d)\n" - " -pubUri [name] Publisher Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -subUri [name] Subscriber Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -qbvOffset [num] QBV offset value (default %d)\n" - " -operBaseTime [location] Bastime file location\n" - " -monotonicOffset [location] Monotonic offset file location\n" - " -disableSoTxtime Do not use SO_TXTIME\n" - " -enableCsvLog Experimental: To log the data in csv files. Support up to 1 million samples\n" - " -enableconsolePrint Experimental: To print the data in console output. Support for higher cycle time\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, \ - DEFAULT_PUBSUBAPP_THREAD_PRIORITY, \ - DEFAULT_PUBSUBAPP_THREAD_CORE, \ - DEFAULT_PUBLISHING_MAC_ADDRESS, DEFAULT_SUBSCRIBING_MAC_ADDRESS, DEFAULT_QBV_OFFSET); -} - -/** - * **Main Server code** - */ -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - char *interface = NULL; - UA_Int32 argInputs = 0; - UA_Int32 long_index = 0; - char *progname; - pthread_t pubSubAppThreadID; - char *operBaseTimeFileName = NULL; - char *monotonicOffsetFileName = NULL; - FILE *operBaseTimefile; - FILE *monotonicOffsetFile; - UA_String transportProfile; - - /* Process the command line arguments */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - - static struct option long_options[] = { - {"interface", required_argument, 0, 'a'}, - {"cycleTimeInMsec", required_argument, 0, 'b'}, - {"socketPriority", required_argument, 0, 'c'}, - {"pubSubAppPriority", required_argument, 0, 'd'}, - {"pubSubAppCore", required_argument, 0, 'e'}, - {"pubUri", required_argument, 0, 'f'}, - {"subUri", required_argument, 0, 'g'}, - {"qbvOffset", required_argument, 0, 'h'}, - {"operBaseTime", required_argument, 0, 'i'}, - {"monotonicOffset", required_argument, 0, 'j'}, - {"disableSoTxtime", no_argument, 0, 'k'}, - {"enableCsvLog", no_argument, 0, 'l'}, - {"enableconsolePrint", no_argument, 0, 'm'}, - {"help", no_argument, 0, 'o'}, - {0, 0, 0, 0 } - }; - - while ((argInputs = getopt_long_only(argc, argv,"", long_options, &long_index)) != -1) { - switch (argInputs) { - case 'a': - interface = optarg; - break; - case 'b': - cycleTimeInMsec = atof(optarg); - break; - case 'c': - socketPriority = atoi(optarg); - break; - case 'd': - pubSubAppPriority = atoi(optarg); - break; - case 'e': - pubSubAppCore = atoi(optarg); - break; - case 'f': - pubUri = optarg; - break; - case 'g': - subUri = optarg; - break; - case 'h': - qbvOffset = atoi(optarg); - break; - case 'i': - operBaseTimeFileName = optarg; - break; - case 'j': - monotonicOffsetFileName = optarg; - break; - case 'k': - disableSoTxtime = UA_FALSE; - break; - case 'l': - enableCsvLog = UA_TRUE; - break; - case 'm': - consolePrint = UA_TRUE; - break; - case 'o': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if (!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run"); - usage(progname); - return 0; - } - - if (cycleTimeInMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeInMsec); - usage(progname); - return -1; - } - -#ifdef TWO_WAY_COMMUNICATION - if (disableSoTxtime == UA_TRUE) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "With sotxtime handling not supported so the application will run without soTxtime"); - disableSoTxtime = UA_FALSE; - } -#endif - - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); -#ifdef TWO_WAY_COMMUNICATION - UA_NetworkAddressUrlDataType networkAddressUrlPub; - - //If you are running for UDP provide ip address as input. Connection creation - //Failed if we provide the interface name - networkAddressUrlPub.networkInterface = UA_STRING(interface); - networkAddressUrlPub.url = UA_STRING(pubUri); -#endif - - UA_NetworkAddressUrlDataType networkAddressUrlSub; - networkAddressUrlSub.networkInterface = UA_STRING(interface); - networkAddressUrlSub.url = UA_STRING(subUri); - transportProfile = UA_STRING(ETH_TRANSPORT_PROFILE); - - if (enableCsvLog) - fpSubscriber = fopen(fileSubscribedData, "w"); - -#ifdef TWO_WAY_COMMUNICATION - if (enableCsvLog) - fpPublisher = fopen(filePublishedData, "w"); -#endif - - /* Initialize arguments required for the thread to run */ - threadArgPubSub1 = (threadArgPubSub *) UA_malloc(sizeof(threadArgPubSub)); - - /* Server is the new OPCUA model which has both publisher and subscriber configuration */ - /* add axis node and OPCUA pubsub client server counter nodes */ - addServerNodes(server); - -#ifdef TWO_WAY_COMMUNICATION - addPubSubConnection(server, &transportProfile, &networkAddressUrlPub); - addPublishedDataSet(server); - _addDataSetField(server); - addWriterGroup(server); - addDataSetWriter(server); - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); - -#endif - - addPubSubConnectionSubscriber(server, &transportProfile, &networkAddressUrlSub); - addReaderGroup(server); - addDataSetReader(server); - UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier); - UA_Server_enableReaderGroup(server, readerGroupIdentifier); - - threadArgPubSub1->server = server; - if (operBaseTimeFileName != NULL) { - long double floatValueBaseTime; - operBaseTimefile = fopen(operBaseTimeFileName, "r"); - fscanf(operBaseTimefile,"%Lf", &floatValueBaseTime); - uint64_t operBaseTimeInNs = (uint64_t)(floatValueBaseTime * SECONDS); - threadArgPubSub1->operBaseTime = operBaseTimeInNs; - } - else - threadArgPubSub1->operBaseTime = 0; - - if (monotonicOffsetFileName != NULL) { - monotonicOffsetFile = fopen(monotonicOffsetFileName, "r"); - char fileParseBuffer[255]; - if (fgets(fileParseBuffer, sizeof(fileParseBuffer), monotonicOffsetFile) != NULL) { - UA_UInt64 monotonicOffsetValueSecondsField = 0; - UA_UInt64 monotonicOffsetValueNanoSecondsField = 0; - UA_UInt64 monotonicOffsetInNs = 0; - const char* monotonicOffsetValueSec = strtok(fileParseBuffer, " "); - if (monotonicOffsetValueSec != NULL) - monotonicOffsetValueSecondsField = (UA_UInt64)(atoll(monotonicOffsetValueSec)); - - const char* monotonicOffsetValueNSec = strtok(NULL, " "); - if (monotonicOffsetValueNSec != NULL) - monotonicOffsetValueNanoSecondsField = (UA_UInt64)(atoll(monotonicOffsetValueNSec)); - - monotonicOffsetInNs = (monotonicOffsetValueSecondsField * (UA_UInt64)(SECONDS)) + monotonicOffsetValueNanoSecondsField; - threadArgPubSub1->monotonicOffset = monotonicOffsetInNs; - } - else - threadArgPubSub1->monotonicOffset = 0; - } - else - threadArgPubSub1->monotonicOffset = 0; - - threadArgPubSub1->packetLossCount = 0; - - char threadNamePubSubApp[22] = "PubSubApp"; - pubSubAppThreadID = threadCreation((UA_Int16)pubSubAppPriority, (size_t)pubSubAppCore, pubSubApp, threadNamePubSubApp, NULL); - - retval |= UA_Server_run(server, &runningServer); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nTotal Packet Loss Count of publisher application :%"PRIu64"\n", \ - threadArgPubSub1->packetLossCount); - UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); - - returnValue = pthread_join(pubSubAppThreadID, NULL); - if (returnValue != 0) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for pubSubApp thread:%d\n", returnValue); - - if (enableCsvLog) { -#ifdef TWO_WAY_COMMUNICATION - /* Write the published data in the publisher_T1.csv file */ - size_t pubLoopVariable = 0; - for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - fprintf(fpPublisher, "%"PRId64",%ld.%09ld\n", - publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec); - } -#endif - /* Write the subscribed data in the subscriber_T8.csv file */ - size_t subLoopVariable = 0; - for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%"PRId64",%ld.%09ld\n", - subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec); - } - } - removeServerNodes(server); - UA_Server_delete(server); - if (operBaseTimeFileName != NULL) - fclose(operBaseTimefile); - - if (monotonicOffsetFileName != NULL) - fclose(monotonicOffsetFile); -#ifdef TWO_WAY_COMMUNICATION - UA_free(runningPub); - UA_free(pubCounterData); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(pubDataValueRT); - UA_free(runningPubDataValueRT); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedDataValueRT[iterator]); - - if (enableCsvLog) - fclose(fpPublisher); -#endif - - UA_free(runningSub); - UA_free(subCounterData); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedCounterData[iterator]); - /* Free external data source */ - UA_free(subDataValueRT); - UA_free(runningSubDataValueRT); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedDataValueRT[iterator]); - UA_free(threadArgPubSub1); - if (enableCsvLog) - fclose(fpSubscriber); - - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/pubsub_TSN_publisher.c b/examples/pubsub_realtime/attic/pubsub_TSN_publisher.c deleted file mode 100644 index 42f25db64..000000000 --- a/examples/pubsub_realtime/attic/pubsub_TSN_publisher.c +++ /dev/null @@ -1,1814 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-tsn-publisher: - * - * Realtime Publish Example - * ------------------------ - * - * This tutorial shows publishing and subscribing information in Realtime. This - * example has both Publisher and Subscriber(used as threads, running in same - * core), the Publisher thread publishes counterdata (an incremental data), that - * is subscribed by the Subscriber thread of pubsub_TSN_loopback.c example. The - * Publisher thread of pusbub_TSN_loopback.c publishes the received counterdata, - * which is subscribed by the Subscriber thread of this example. Thus a - * round-trip of counterdata is achieved. In a realtime system, the round-trip - * time of the counterdata is 4x cycletime, in this example, the round-trip time - * is 1ms. The flow of this communication and the trace points are given in the - * diagram below. - * - * Another thread called the UserApplication thread is also used in the example, - * which serves the functionality of the Control loop. In this example, - * UserApplication threads increments the counterData, which is published by the - * Publisher thread and also reads the subscribed data from the Information - * Model and writes the updated counterdata into distinct csv files during each - * cycle. Buffered Network Message will be used for publishing and subscribing - * in the RT path. Further, DataSetField will be accessed via direct pointer - * access between the user interface and the Information Model. - * - * To ensure realtime capabilities, Publisher uses ETF(Earliest Tx-time First) - * to publish information at the calculated tranmission time over Ethernet. - * Subscriber can be used with or without XDP(Xpress Data Processing) over - * Ethernet - * - * Run step of the example is as mentioned below: - * - * ``./bin/examples/pubsub_TSN_publisher -interface `` - * - * For more options, run ./bin/examples/pubsub_TSN_publisher -help */ - -/* Trace point setup - * - * +--------------+ +----------------+ - * T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4 - * | | Application | ^ | | Application | ^ - * | +--------------+ | | +----------------+ | - * User | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * -----------|--------------|------------------|----------------|-------- - * | | Node 1 | | | | Node 2 | | - * Kernel | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * v +--------------+ | v +----------------+ | - * T2 | TX tcpdump | T7<----------T6 | RX tcpdump | T3 - * | +--------------+ +----------------+ ^ - * | | - * ---------------------------------------------------------- - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -/* For thread operations */ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "ua_pubsub.h" - -#include -#include - -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; - -UA_DataSetReaderConfig readerConfig; - -/* to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_TSN_publisher */ - -/* Configurable Parameters */ -/* These defines enables the publisher and subscriber of the OPCUA stack */ -/* To run only publisher, enable PUBLISHER define alone (comment SUBSCRIBER) */ -#define PUBLISHER -/* To run only subscriber, enable SUBSCRIBER define alone (comment PUBLISHER) */ -#define SUBSCRIBER -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define DEFAULT_QBV_OFFSET 125 -#define DEFAULT_SOCKET_PRIORITY 3 -#if defined(PUBLISHER) -#define PUBLISHER_ID 2234 -#define WRITER_GROUP_ID 101 -#define DATA_SET_WRITER_ID 62541 -#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3" -#endif -#define PUBLISHER_ID_SUB 2235 -#define WRITER_GROUP_ID_SUB 100 -#define DATA_SET_WRITER_ID_SUB 62541 -#define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.3" -#define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes -#define PORT_NUMBER 62541 -#define DEFAULT_XDP_QUEUE 2 -#define PUBSUB_CONFIG_RT_INFORMATION_MODEL - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000 * 1000 -#define SECONDS 1000 * 1000 * 1000 -#define SECONDS_SLEEP 5 -/* Publisher will sleep for 60% of cycle time and then prepares the */ -/* transmission packet within 40% */ -static UA_Double pubWakeupPercentage = 0.6; -#if defined(SUBSCRIBER) -/* Subscriber will wakeup only during start of cycle and check whether */ -/* the packets are received */ -static UA_Double subWakeupPercentage = 0; -#endif -/* User application Pub/Sub will wakeup at the 30% of cycle time and handles the */ -/* user data such as read and write in Information model */ -static UA_Double userAppWakeupPercentage = 0.3; -/* Priority of Publisher, Subscriber, User application and server are kept */ -/* after some prototyping and analyzing it */ -#define DEFAULT_PUB_SCHED_PRIORITY 78 -#define DEFAULT_SUB_SCHED_PRIORITY 81 -#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75 -#define MAX_MEASUREMENTS 1000000 -#define MAX_MEASUREMENTS_FILEWRITE 100000000 -#define DEFAULT_PUB_CORE 2 -#define DEFAULT_SUB_CORE 2 -#define DEFAULT_USER_APP_CORE 3 -#define SECONDS_INCREMENT 1 -#ifndef CLOCK_TAI -#define CLOCK_TAI 11 -#endif -#define CLOCKID CLOCK_TAI -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define LATENCY_CSV_FILE_NAME "latencyT1toT8.csv" - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION -#define UA_AES128CTR_SIGNING_KEY_LENGTH 32 -#define UA_AES128CTR_KEY_LENGTH 16 -#define UA_AES128CTR_KEYNONCE_LENGTH 4 - -#if defined(PUBLISHER) -UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; -UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0}; -UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; -#endif - -#if defined(SUBSCRIBER) -UA_Byte signingKeySub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0}; -UA_Byte encryptingKeySub[UA_AES128CTR_KEY_LENGTH] = {0}; -UA_Byte keyNonceSub[UA_AES128CTR_KEYNONCE_LENGTH] = {0}; -#endif -#endif - -/* If the Hardcoded publisher/subscriber MAC addresses need to be changed, - * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS - */ - -/* Set server running as true */ -UA_Boolean runningServer = true; -char* pubMacAddress = DEFAULT_PUBLISHING_MAC_ADDRESS; -char* subMacAddress = DEFAULT_SUBSCRIBING_MAC_ADDRESS; -static UA_Double cycleTimeInMsec = DEFAULT_CYCLE_TIME; -static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY; -static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY; -static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY; -static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY; -static UA_Int32 pubCore = DEFAULT_PUB_CORE; -static UA_Int32 subCore = DEFAULT_SUB_CORE; -static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE; -static UA_Int32 qbvOffset = DEFAULT_QBV_OFFSET; -static UA_UInt32 xdpQueue = DEFAULT_XDP_QUEUE; -static UA_UInt32 xdpFlag = XDP_FLAGS_SKB_MODE; -static UA_UInt32 xdpBindFlag = XDP_COPY; -static UA_Boolean disableSoTxtime = true; -static UA_Boolean enableCsvLog = false; -static UA_Boolean enableLatencyCsvLog = false; -static UA_Boolean consolePrint = false; -static UA_Boolean signalTerm = false; -static UA_Boolean enableXdpSubscribe = false; - -/* Variables corresponding to PubSub connection creation, - * published data set and writer group */ -UA_NodeId connectionIdent; -UA_NodeId publishedDataSetIdent; -UA_NodeId writerGroupIdent; -UA_NodeId pubNodeID; -UA_NodeId subNodeID; -UA_NodeId pubRepeatedCountNodeID; -UA_NodeId subRepeatedCountNodeID; -UA_NodeId runningPubStatusNodeID; -UA_NodeId runningSubStatusNodeID; -/* Variables for counter data handling in address space */ -UA_UInt64 *pubCounterData = NULL; -UA_DataValue *pubDataValueRT = NULL; -UA_Boolean *runningPub = NULL; -UA_DataValue *runningPubDataValueRT = NULL; -UA_UInt64 *repeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *repeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -UA_UInt64 *subCounterData = NULL; -UA_DataValue *subDataValueRT = NULL; -UA_Boolean *runningSub = NULL; -UA_DataValue *runningSubDataValueRT = NULL; -UA_UInt64 *subRepeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *subRepeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -/** - * CSV file handling - * ~~~~~~~~~~~~~~~~~ - * - * CSV files are written for Publisher and Subscriber thread. csv files include - * the counterdata that is being either Published or Subscribed along with the - * timestamp. These csv files can be used to compute latency for following - * combinations of Tracepoints, T1-T4 and T1-T8. - * - * T1-T8 - Gives the Round-trip time of a counterdata, as the value published by - * the Publisher thread in pubsub_TSN_publisher.c example is subscribed by the - * Subscriber thread in pubsub_TSN_loopback.c example and is published back to - * the pubsub_TSN_publisher.c example */ - -#if defined(PUBLISHER) -/* File to store the data and timestamps for different traffic */ -FILE *fpPublisher; -char *filePublishedData = "publisher_T1.csv"; -/* Array to store published counter data */ -UA_UInt64 publishCounterValue[MAX_MEASUREMENTS]; -size_t measurementsPublisher = 0; -/* Array to store timestamp */ -struct timespec publishTimestamp[MAX_MEASUREMENTS]; -/* Thread for publisher */ -pthread_t pubthreadID; -struct timespec dataModificationTime; -#endif - -#if defined(SUBSCRIBER) -/* File to store the data and timestamps for different traffic */ -FILE *fpSubscriber; -char *fileSubscribedData = "subscriber_T8.csv"; -/* Array to store subscribed counter data */ -UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS]; -size_t measurementsSubscriber = 0; -/* Array to store timestamp */ -struct timespec subscribeTimestamp[MAX_MEASUREMENTS]; -/* Thread for subscriber */ -pthread_t subthreadID; -/* Variable for PubSub connection creation */ -UA_NodeId connectionIdentSubscriber; -struct timespec dataReceiveTime; -#endif - -/* Thread for user application*/ -pthread_t userApplicationThreadID; - -/* Base time handling for the threads */ -struct timespec threadBaseTime; -UA_Boolean baseTimeCalculated = false; - -typedef struct { -UA_Server* ServerRun; -} serverConfigStruct; - -/* Structure to define thread parameters */ -typedef struct { -UA_Server* server; -void* data; -UA_ServerCallback callback; -UA_Duration interval_ms; -UA_UInt64* callbackId; -} threadArg; - -/** - * Function calls for different threads */ -/* Publisher thread routine for ETF */ -void *publisherETF(void *arg); -/* Subscriber thread routine */ -void *subscriber(void *arg); -/* User application thread routine */ -void *userApplicationPubSub(void *arg); -/* For adding nodes in the server information model */ -static void addServerNodes(UA_Server *server); -/* For deleting the nodes created */ -static void removeServerNodes(UA_Server *server); -/* To create multi-threads */ -static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread)(void *), - char *applicationName, void *serverConfig); - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - signalTerm = true; -} - -/** - * **Nanosecond field handling** - * - * Nanosecond field in timespec is checked for overflowing and one second - * is added to seconds field and nanosecond field is set to zero -*/ -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while(timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= SECONDS; - } - -} - -/** - * **Custom callback handling** - * - * Custom callback thread handling overwrites the default timer based - * callback function with the custom (user-specified) callback interval. */ -/* Add a callback for cyclic repetition */ -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - /* Initialize arguments required for the thread to run */ - threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg)); - - /* Pass the value required for the threads */ - threadArguments->server = server; - threadArguments->data = data; - threadArguments->callback = callback; - threadArguments->interval_ms = interval_ms; - threadArguments->callbackId = callbackId; - - /* Check the writer group identifier and create the thread accordingly */ - if(UA_NodeId_equal(&identifier, &writerGroupIdent)) { -#if defined(PUBLISHER) - /* Create the publisher thread with the required priority and core affinity */ - char threadNamePub[10] = "Publisher"; - *callbackId = threadCreation((UA_Int16)pubPriority, (size_t)pubCore, - publisherETF, threadNamePub, threadArguments); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Publisher thread callback Id: %lu\n", (unsigned long)*callbackId); -#endif - } - else { -#if defined(SUBSCRIBER) - /* Create the subscriber thread with the required priority and core affinity */ - char threadNameSub[11] = "Subscriber"; - *callbackId = threadCreation((UA_Int16)subPriority, (size_t)subCore, - subscriber, threadNameSub, threadArguments); - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Subscriber thread callback Id: %lu\n", (unsigned long)*callbackId); -#endif - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - /* Callback interval need not be modified as it is thread based implementation. - * The thread uses nanosleep for calculating cycle time and modification in - * nanosleep value changes cycle time */ - return UA_STATUSCODE_GOOD; -} - -/* Remove the callback added for cyclic repetition */ -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId) { - if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0)) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Pthread Join Failed thread: %lu\n", (unsigned long)callbackId); -} - -/** - * External data source handling - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and - * assure that the write leads not to synchronization issues and race - * conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -/** - * Subscriber - * ~~~~~~~~~~ - * - * Create connection, readergroup, datasetreader, subscribedvariables for the - * Subscriber thread. */ - -#if defined(SUBSCRIBER) -static void -addPubSubConnectionSubscriber(UA_Server *server, - UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Subscriber Connection"); - connectionConfig.enabled = true; - - UA_KeyValuePair connectionOptions[4]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = enableXdpSubscribe; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); - UA_UInt32 flags = xdpFlag; - UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); - UA_UInt32 rxqueue = xdpQueue; - UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); - UA_UInt32 bindflags = xdpBindFlag; - UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 4; - - - UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.uint32 = UA_UInt32_random(); - retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber); - if(retval == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "The PubSub Connection was created successfully!"); -} - -/* Add ReaderGroup to the created connection */ -static void -addReaderGroup(UA_Server *server) { - if(server == NULL) - return; - - UA_ReaderGroupConfig readerGroupConfig; - memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup1"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Encryption settings */ - UA_ServerConfig *config = UA_Server_getConfig(server); - readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[1]; -#endif - - UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, - &readerGroupIdentifier); - UA_Server_enableReaderGroup(server, readerGroupIdentifier); - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeySub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeySub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonceSub}; - // TODO security token not necessary for readergroup (extracted from security-header) - UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn); -#endif -} - - -/* Set SubscribedDataSet type to TargetVariables data type - * Add SubscriberCounter variable to the DataSetReader */ -static void -addSubscribedVariables(UA_Server *server) { - UA_Int32 iterator = 0; - UA_Int32 iteratorRepeatedCount = 0; - if(server == NULL) { - return; - } - - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) - UA_calloc((REPEATED_NODECOUNTS + 2), sizeof(UA_FieldTargetVariable)); - if(!targetVars) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "FieldTargetVariable - Bad out of memory"); - return; - } - - runningSub = UA_Boolean_new(); - if(!runningSub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningsub - Bad out of memory"); - return; - } - - *runningSub = true; - runningSubDataValueRT = UA_DataValue_new(); - if(!runningSubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "runningsubDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningSubDataValueRT->value, runningSub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningSubDataValueRT->hasValue = true; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningSubvalueBackend; - runningSubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningSubvalueBackend.backend.external.value = &runningSubDataValueRT; - runningSubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningSubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)30000), runningSubvalueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - iterator++; - /* For creating Targetvariable */ - for(iterator = 1, iteratorRepeatedCount = 0; iterator <= REPEATED_NODECOUNTS; iterator++, iteratorRepeatedCount++) - { - subRepeatedCounterData[iteratorRepeatedCount] = UA_UInt64_new(); - if(!subRepeatedCounterData[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeRepeatedCounterData - Bad out of memory"); - return; - } - - *subRepeatedCounterData[iteratorRepeatedCount] = 0; - subRepeatedDataValueRT[iteratorRepeatedCount] = UA_DataValue_new(); - if(!subRepeatedDataValueRT[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "SubscribeRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&subRepeatedDataValueRT[iteratorRepeatedCount]->value, - subRepeatedCounterData[iteratorRepeatedCount], &UA_TYPES[UA_TYPES_UINT64]); - subRepeatedDataValueRT[iteratorRepeatedCount]->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subRepeatedDataValueRT[iteratorRepeatedCount]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount+50000), valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount + 50000); - } - - subCounterData = UA_UInt64_new(); - if(!subCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeCounterData - Bad out of memory"); - return; - } - - *subCounterData = 0; - subDataValueRT = UA_DataValue_new(); - if(!subDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]); - subDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeID, valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = subNodeID; - - /* Set the subscribed data to TargetVariable type */ - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = REPEATED_NODECOUNTS + 2; -} - -/* Add DataSetReader to the ReaderGroup */ -static void -addDataSetReader(UA_Server *server) { - UA_Int32 iterator = 0; - if(server == NULL) { - return; - } - - memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING("DataSet Reader 1"); - UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB; - readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; - readerConfig.publisherId.data = &publisherIdentifier; - readerConfig.writerGroupId = WRITER_GROUP_ID_SUB; - readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB; - - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = - (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = REPEATED_NODECOUNTS + 2; - pMetaData->fields = (UA_FieldMetaData*) - UA_Array_new(pMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]); - - /* Boolean DataType */ - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_BOOLEAN].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_BOOLEAN; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - iterator++; - for(iterator = 1; iterator <= REPEATED_NODECOUNTS; iterator++) { - UA_FieldMetaData_init(&pMetaData->fields[iterator]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - } - - /* Unsigned Integer DataType */ - UA_FieldMetaData_init(&pMetaData->fields[iterator]); - UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - - /* Setup Target Variables in DSR config */ - addSubscribedVariables(server); - - /* Setting up Meta data configuration in DataSetReader */ - UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); -} -#endif - -#if defined(PUBLISHER) - -/** - * Publisher - * ~~~~~~~~~ - * - * Create connection, writergroup, datasetwriter and publisheddataset for - * Publisher thread. */ - -static void -addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){ - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Publisher Connection"); - connectionConfig.enabled = true; - UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub; - connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE); - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_Variant_setScalar(&connectionOptions[0].value, &socketPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Variant_setScalar(&connectionOptions[1].value, &disableSoTxtime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); -} - -/* PublishedDataSet handling */ -static void -addPublishedDataSet(UA_Server *server) { - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); -} - -/* DataSetField handling */ -static void -_addDataSetField(UA_Server *server) { - /* Add a field to the previous created PublishedDataSet */ - UA_NodeId dataSetFieldIdentRepeated; - UA_DataSetFieldConfig dataSetFieldConfig; -#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS - staticValueSource = UA_DataValue_new(); -#endif - - UA_NodeId dataSetFieldIdentRunning; - UA_DataSetFieldConfig dsfConfigPubStatus; - memset(&dsfConfigPubStatus, 0, sizeof(UA_DataSetFieldConfig)); - - runningPub = UA_Boolean_new(); - if(!runningPub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPub - Bad out of memory"); - return; - } - - *runningPub = true; - runningPubDataValueRT = UA_DataValue_new(); - if(!runningPubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPubDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningPubDataValueRT->value, runningPub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningPubDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningPubvalueBackend; - runningPubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningPubvalueBackend.backend.external.value = &runningPubDataValueRT; - runningPubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningPubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)20000), runningPubvalueBackend); - - /* setup RT DataSetField config */ - dsfConfigPubStatus.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfigPubStatus.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfigPubStatus, &dataSetFieldIdentRunning); - - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - - repeatedCounterData[iterator] = UA_UInt64_new(); - if(!repeatedCounterData[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounter - Bad out of memory"); - return; - } - - *repeatedCounterData[iterator] = 0; - repeatedDataValueRT[iterator] = UA_DataValue_new(); - if(!repeatedDataValueRT[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&repeatedDataValueRT[iterator]->value, repeatedCounterData[iterator], &UA_TYPES[UA_TYPES_UINT64]); - repeatedDataValueRT[iterator]->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[iterator]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000), valueBackend); - - /* setup RT DataSetField config */ - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = true; - dataSetFieldConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdentRepeated); - } - - UA_NodeId dataSetFieldIdent; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - pubCounterData = UA_UInt64_new(); - if(!pubCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishCounter - Bad out of memory"); - return; - } - - *pubCounterData = 0; - pubDataValueRT = UA_DataValue_new(); - if(!pubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]); - pubDataValueRT->hasValue = true; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &pubDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, pubNodeID, valueBackend); - - /* setup RT DataSetField config */ - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = true; - dsfConfig.field.variable.publishParameters.publishedVariable = pubNodeID; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); - -} - -/* WriterGroup handling */ -static void -addWriterGroup(UA_Server *server) { - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = cycleTimeInMsec; - writerGroupConfig.enabled = false; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.writerGroupId = WRITER_GROUP_ID; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - UA_ServerConfig *config = UA_Server_getConfig(server); - writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; - writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0]; -#endif - - /* The configuration flags for the messages are encapsulated inside the - * message- and transport settings extension objects. These extension - * objects are defined by the standard. e.g. - * UadpWriterGroupMessageDataType */ - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - /* Change message settings of writerGroup to send PublisherId, - * WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader - * of NetworkMessage */ - writerGroupMessage->networkMessageContentMask = - (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); - -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION - /* Add the encryption key informaton */ - UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKeyPub}; - UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKeyPub}; - UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNoncePub}; - UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn); -#endif - -} - -/* DataSetWriter handling */ -static void -addDataSetWriter(UA_Server *server) { - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); -} - -/** - * Published data handling - * ~~~~~~~~~~~~~~~~~~~~~~~ - * - * The published data is updated in the array using this function. */ - -#if defined(PUBLISHER) -static void -updateMeasurementsPublisher(struct timespec start_time, - UA_UInt64 counterValue) { - if(measurementsPublisher >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Publisher: Maximum log measurements reached - Closing the application"); - signalTerm = true; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%lu,%ld.%09ld\n", - (long unsigned)counterValue, start_time.tv_sec, start_time.tv_nsec); - - if(signalTerm != true){ - publishTimestamp[measurementsPublisher] = start_time; - publishCounterValue[measurementsPublisher] = counterValue; - measurementsPublisher++; - } -} -#endif -#if defined(SUBSCRIBER) - -/** - * Subscribed data handling - * ~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The subscribed data is updated in the array using this function Subscribed - * data handling. */ - -static void -updateMeasurementsSubscriber(struct timespec receive_time, - UA_UInt64 counterValue) { - if(measurementsSubscriber >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Subscriber: Maximum log measurements reached - Closing the application"); - signalTerm = true; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%lu,%ld.%09ld\n", - (long unsigned)counterValue, receive_time.tv_sec, receive_time.tv_nsec); - - if(signalTerm != true) - { - subscribeTimestamp[measurementsSubscriber] = receive_time; - subscribeCounterValue[measurementsSubscriber] = counterValue; - measurementsSubscriber++; - } -} -#endif - -/** - * Publisher thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This is the Publisher thread that sleeps for 60% of the cycletime (250us) and - * prepares the tranmission packet within 40% of cycletime. The priority of this - * thread is lower than the priority of the Subscriber thread, so the subscriber - * thread executes first during every cycle. The data published by this thread - * in one cycle is subscribed by the subscriber thread of pubsub_TSN_loopback in - * the next cycle(two cycle timing model). - * - * The publisherETF function is the routine used by the publisher thread. */ -void *publisherETF(void *arg) { - struct timespec nextnanosleeptime; - UA_ServerCallback pubCallback; - UA_Server* server; - UA_WriterGroup* currentWriterGroup; // TODO: Remove WriterGroup Usage - UA_UInt64 interval_ns; - UA_UInt64 transmission_time; - - threadArg *threadArgumentsPublisher = (threadArg *)arg; - server = threadArgumentsPublisher->server; - pubCallback = threadArgumentsPublisher->callback; - currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data; - interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS); - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptime.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the pubWakeUp percentage */ - nextnanosleeptime.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * pubWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptime); - - /* Define Ethernet ETF transport settings */ - UA_EthernetWriterGroupTransportDataType ethernettransportSettings; - memset(ðernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType)); - ethernettransportSettings.transmission_time = 0; - - /* Encapsulate ETF config in transportSettings */ - UA_ExtensionObject transportSettings; - memset(&transportSettings, 0, sizeof(UA_ExtensionObject)); - /* TODO: transportSettings encoding and type to be defined */ - transportSettings.content.decoded.data = ðernettransportSettings; - currentWriterGroup->config.transportSettings = transportSettings; - UA_UInt64 roundOffCycleTime = (UA_UInt64)((cycleTimeInMsec * MILLI_SECONDS) - - (cycleTimeInMsec * MILLI_SECONDS * pubWakeupPercentage)); - - while(*runningPub) { - /* The Publisher threads wakes up at the configured publisher wake up - * percentage (60%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL); - /* Whenever Ctrl + C pressed, publish running boolean as false to stop - * the subscriber before terminating the application */ - if(signalTerm == true) - *runningPub = false; - - /* Calculation of transmission time using the configured qbv offset by - * the user - Will be handled by publishingOffset in the future */ - transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + - roundOffCycleTime + (UA_UInt64)(qbvOffset * 1000); - ethernettransportSettings.transmission_time = transmission_time; - /* Publish the data using the pubcallback - UA_WriterGroup_publishCallback() */ - pubCallback(server, currentWriterGroup); - /* Calculation of the next wake up time by adding the interval with the previous wake up time */ - nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns; - nanoSecondFieldConversion(&nextnanosleeptime); - } - -#if defined(PUBLISHER) && !defined(SUBSCRIBER) - runningServer = UA_FALSE; -#endif - UA_free(threadArgumentsPublisher); - return NULL; -} -#endif - -#if defined(SUBSCRIBER) - -/** - * Subscriber thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This Subscriber thread will wakeup during the start of cycle at 250us - * interval and check if the packets are received. Subscriber thread has the - * highest priority. This Subscriber thread subscribes to the data published by - * the Publisher thread of pubsub_TSN_loopback in the previous cycle. The - * subscriber function is the routine used by the subscriber thread. */ - -void *subscriber(void *arg) { - UA_Server* server; - void* currentReaderGroup; - UA_ServerCallback subCallback; - struct timespec nextnanosleeptimeSub; - UA_UInt64 subInterval_ns; - - threadArg *threadArgumentsSubscriber = (threadArg *)arg; - server = threadArgumentsSubscriber->server; - subCallback = threadArgumentsSubscriber->callback; - currentReaderGroup = threadArgumentsSubscriber->data; - subInterval_ns = (UA_UInt64)(threadArgumentsSubscriber->interval_ms * MILLI_SECONDS); - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptimeSub.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the subWakeUp percentage */ - nextnanosleeptimeSub.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * subWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptimeSub); - while(*runningSub) { - /* The Subscriber threads wakes up at the configured subscriber wake up percentage (0%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL); - /* Receive and process the incoming data */ - subCallback(server, currentReaderGroup); - /* Calculation of the next wake up time by adding the interval with the previous wake up time */ - nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)subInterval_ns; - nanoSecondFieldConversion(&nextnanosleeptimeSub); - - /* Whenever Ctrl + C pressed, modify the runningSub boolean to false to end this while loop */ - if(signalTerm == true) - *runningSub = false; - } - - UA_free(threadArgumentsSubscriber); - /* While ctrl+c is provided in publisher side then loopback application - * need to be closed by after sending *running=0 for subscriber T4 */ - if(*runningSub == false) - signalTerm = true; - - sleep(1); - runningServer = false; - return NULL; -} -#endif - -#if defined(PUBLISHER) || defined(SUBSCRIBER) - -/** - * UserApplication thread routine - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The userapplication thread will wakeup at 30% of cycle time and handles the - * userdata(read and write in Information Model). This thread serves the purpose - * of a Control loop, which is used to increment the counterdata to be published - * by the Publisher thread and read the data from Information Model for the - * Subscriber thread and writes the updated counterdata in distinct csv files - * for both threads. */ - -void *userApplicationPubSub(void *arg) { - UA_UInt64 repeatedCounterValue = 10; - struct timespec nextnanosleeptimeUserApplication; - /* Verify whether baseTime has already been calculated */ - if(!baseTimeCalculated) { - /* Get current time and compute the next nanosleeptime */ - clock_gettime(CLOCKID, &threadBaseTime); - /* Variable to nano Sleep until SECONDS_SLEEP second boundary */ - threadBaseTime.tv_sec += SECONDS_SLEEP; - threadBaseTime.tv_nsec = 0; - baseTimeCalculated = true; - } - - nextnanosleeptimeUserApplication.tv_sec = threadBaseTime.tv_sec; - /* Modify the nanosecond field to wake up at the userAppWakeUp percentage */ - nextnanosleeptimeUserApplication.tv_nsec = threadBaseTime.tv_nsec + - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS * userAppWakeupPercentage); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - *pubCounterData = 0; - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - *repeatedCounterData[iterator] = repeatedCounterValue; - } - -#if defined(PUBLISHER) && defined(SUBSCRIBER) - while(*runningPub || *runningSub) { -#else - while(*runningPub) { -#endif - /* The User application threads wakes up at the configured userApp wake - * up percentage (30%) of each cycle */ - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL); -#if defined(PUBLISHER) - /* Increment the counter data and repeated counter data for the next cycle publish */ - *pubCounterData = *pubCounterData + 1; - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - *repeatedCounterData[iterator] = *repeatedCounterData[iterator] + 1; - - /* Get the time - T1, time where the counter data and repeated counter - * data gets incremented. As this application uses FPM, we do not - * require explicit call of UA_Server_write() to write the counter - * values to the Information model. Hence, we take publish T1 time - * here. */ - clock_gettime(CLOCKID, &dataModificationTime); -#endif - -#if defined(SUBSCRIBER) - /* Get the time - T8, time where subscribed varibles are read from the - * Information model. At this point, the packet will be already - * subscribed and written into the Information model. As this - * application uses FPM, we do not require explicit call of - * UA_Server_read() to read the subscribed value from the Information - * model. Hence, we take subscribed T8 time here. */ - clock_gettime(CLOCKID, &dataReceiveTime); -#endif - - /* Update the T1, T8 time with the counter data in the user defined - * publisher and subscriber arrays. */ - if(enableCsvLog || enableLatencyCsvLog || consolePrint) { -#if defined(PUBLISHER) - updateMeasurementsPublisher(dataModificationTime, *pubCounterData); -#endif - -#if defined(SUBSCRIBER) - if(*subCounterData > 0) - updateMeasurementsSubscriber(dataReceiveTime, *subCounterData); -#endif - } - - /* Calculation of the next wake up time by adding the interval with the - * previous wake up time. */ - nextnanosleeptimeUserApplication.tv_nsec += - (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimeUserApplication); - } - - return NULL; -} -#endif - -/** - * Thread creation - * ~~~~~~~~~~~~~~~ - * - * The threadcreation functionality creates thread with given threadpriority, - * coreaffinity. The function returns the threadID of the newly created thread. */ - -static pthread_t -threadCreation(UA_Int16 threadPriority, size_t coreAffinity, - void *(*thread)(void *), char *applicationName, void *serverConfig){ - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if(returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n"); - exit(1); - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\ - "\npthread_setschedparam:%s Thread priority is %d \n", \ - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET(coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if(errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if(returnValue != 0) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - ":%s Cannot create thread\n", applicationName); - - if(CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "%s CPU CORE: %lu\n", applicationName, (unsigned long)coreAffinity); - - return threadID; -} - -/** - * Creation of nodes - * ~~~~~~~~~~~~~~~~~ - * - * The addServerNodes function is used to create the publisher and subscriber - * nodes. */ - -static void addServerNodes(UA_Server *server) { - UA_NodeId objectId; - UA_NodeId newNodeId; - UA_ObjectAttributes object = UA_ObjectAttributes_default; - object.displayName = UA_LOCALIZEDTEXT("en-US", "Counter Object"); - UA_Server_addObjectNode(server, UA_NODEID_NULL, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Counter Object"), UA_NODEID_NULL, - object, NULL, &objectId); - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - UA_UInt64 publishValue = 0; - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter"); - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - newNodeId = UA_NODEID_STRING(1, "PublisherCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher Counter"), - UA_NODEID_NULL, publisherAttr, NULL, &pubNodeID); - UA_VariableAttributes subscriberAttr = UA_VariableAttributes_default; - UA_UInt64 subscribeValue = 0; - subscriberAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&subscriberAttr.value, &subscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - subscriberAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber Counter"); - subscriberAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - newNodeId = UA_NODEID_STRING(1, "SubscriberCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber Counter"), - UA_NODEID_NULL, subscriberAttr, NULL, &subNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodePub = UA_VariableAttributes_default; - UA_UInt64 repeatedPublishValue = 0; - repeatedNodePub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&repeatedNodePub.value, &repeatedPublishValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodePub.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher RepeatedCounter"); - repeatedNodePub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher RepeatedCounter"), - UA_NODEID_NULL, repeatedNodePub, NULL, &pubRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusPub = UA_VariableAttributes_default; - UA_Boolean runningPubStatus = 0; - runningStatusPub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusPub.value, &runningPubStatus, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusPub.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Pub"); - runningStatusPub.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Pub"), - UA_NODEID_NULL, runningStatusPub, NULL, &runningPubStatusNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodeSub = UA_VariableAttributes_default; - UA_UInt64 repeatedSubscribeValue; - UA_Variant_setScalar(&repeatedNodeSub.value, &repeatedSubscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodeSub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodeSub.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber RepeatedCounter"); - repeatedNodeSub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+50000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber RepeatedCounter"), - UA_NODEID_NULL, repeatedNodeSub, NULL, &subRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusSubscriber = UA_VariableAttributes_default; - UA_Boolean runningSubStatusValue = 0; - runningStatusSubscriber.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusSubscriber.value, &runningSubStatusValue, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusSubscriber.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Sub"); - runningStatusSubscriber.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Sub"), - UA_NODEID_NULL, runningStatusSubscriber, NULL, &runningSubStatusNodeID); - -} - -/** - * Deletion of nodes - * ~~~~~~~~~~~~~~~~~~ - * - * The removeServerNodes function is used to delete the publisher and subscriber - * nodes. */ - -static void removeServerNodes(UA_Server *server) { - /* Delete the Publisher Counter Node*/ - UA_Server_deleteNode(server, pubNodeID, true); - UA_NodeId_clear(&pubNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_Server_deleteNode(server, pubRepeatedCountNodeID, true); - UA_NodeId_clear(&pubRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningPubStatusNodeID, true); - UA_NodeId_clear(&runningPubStatusNodeID); - - UA_Server_deleteNode(server, subNodeID, true); - UA_NodeId_clear(&subNodeID); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) { - UA_Server_deleteNode(server, subRepeatedCountNodeID, true); - UA_NodeId_clear(&subRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningSubStatusNodeID, true); - UA_NodeId_clear(&runningSubStatusNodeID); -} - -#if defined (PUBLISHER) && defined(SUBSCRIBER) - -/** - * Time Difference Calculation - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This function is used to calculate the difference between the - * publishertimestamp and subscribertimestamp and store the result. */ - -static void -timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result) { - if((stop->tv_nsec - start->tv_nsec) < 0) { - result->tv_sec = stop->tv_sec - start->tv_sec - 1; - result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000; - } else { - result->tv_sec = stop->tv_sec - start->tv_sec; - result->tv_nsec = stop->tv_nsec - start->tv_nsec; - } - - return; -} - -/** - * Latency Calculation - * ~~~~~~~~~~~~~~~~~~~ - * - * When the application is run with "-enableLatencyCsvLog" option, this function - * gets executed. This function calculates latency by computing the - * publishtimestamp and subscribetimestamp taking the counterdata as reference - * and writes the result in a csv. */ - -static void computeLatencyAndGenerateCsv(char *latencyFileName) { - /* Character array of computed latency to write into a file */ - static char latency_measurements[MAX_MEASUREMENTS_FILEWRITE]; - struct timespec resultTime; - size_t latencyComputePubIndex, latencyComputeSubIndex; - UA_Double finalTime; - UA_UInt64 missed_counter = 0; - UA_UInt64 repeated_counter = 0; - UA_UInt64 latencyCharIndex = 0; - UA_UInt64 prevLatencyCharIndex = 0; - /* Create the latency file and include the headers */ - FILE *fp_latency; - fp_latency = fopen(latencyFileName, "w"); - latencyCharIndex += (UA_UInt64)sprintf(&latency_measurements[latencyCharIndex], - "%s, %s, %s\n", - "LatencyRTT", "Missed Counters", "Repeated Counters"); - - for(latencyComputePubIndex = 0, latencyComputeSubIndex = 0; - latencyComputePubIndex < measurementsPublisher && latencyComputeSubIndex < measurementsSubscriber; ) { - /* Compute RTT latency by equating counter values */ - if(publishCounterValue[latencyComputePubIndex] == subscribeCounterValue[latencyComputeSubIndex]) { - timespec_diff(&publishTimestamp[latencyComputePubIndex], &subscribeTimestamp[latencyComputeSubIndex], &resultTime); - finalTime = ((UA_Double)((resultTime.tv_sec * 1000000000L) + resultTime.tv_nsec))/1000; - latencyComputePubIndex++; - latencyComputeSubIndex++; - } - else if(publishCounterValue[latencyComputePubIndex] < subscribeCounterValue[latencyComputeSubIndex]) { - timespec_diff(&publishTimestamp[latencyComputePubIndex], &subscribeTimestamp[latencyComputeSubIndex], &resultTime); - finalTime = ((UA_Double)((resultTime.tv_sec * 1000000000L) + resultTime.tv_nsec))/1000; - missed_counter++; - latencyComputePubIndex++; - } - else { - if(subscribeCounterValue[latencyComputeSubIndex - 1] == subscribeCounterValue[latencyComputeSubIndex]) - repeated_counter++; - - timespec_diff(&publishTimestamp[latencyComputePubIndex], &subscribeTimestamp[latencyComputeSubIndex], &resultTime); - finalTime = ((UA_Double)((resultTime.tv_sec * 1000000000L) + resultTime.tv_nsec))/1000; - latencyComputeSubIndex++; - } - - if(((latencyCharIndex - prevLatencyCharIndex) + latencyCharIndex + 3) < MAX_MEASUREMENTS_FILEWRITE) { - latencyCharIndex += (UA_UInt64)sprintf(&latency_measurements[latencyCharIndex], - "%0.3f, %lu, %lu\n", - finalTime, (unsigned long)missed_counter, (unsigned long)repeated_counter); - } - else { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Character array has been filled. Computation stopped and leading to latency csv generation"); - break; - } - - prevLatencyCharIndex = latencyCharIndex; - } - - /* Write into the latency file */ - fwrite(&latency_measurements[0], (size_t)prevLatencyCharIndex, 1, fp_latency); - fclose(fp_latency); -} -#endif -/** - * Usage function - * ~~~~~~~~~~~~~~ - * - * The usage function gives the information to run the application. - * - * ``./bin/examples/pubsub_TSN_publisher -interface `` runs the application. - * - * For more options, use ./bin/examples/pubsub_TSN_publisher -h. */ -static void usage(char *appname) -{ - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -interface [name] Use network interface 'name'\n" - " -cycleTimeInMsec [num] Cycle time in milli seconds (default %lf)\n" - " -socketPriority [num] Set publisher SO_PRIORITY to (default %d)\n" - " -pubPriority [num] Publisher thread priority value (default %d)\n" - " -subPriority [num] Subscriber thread priority value (default %d)\n" - " -userAppPriority [num] User application thread priority value (default %d)\n" - " -pubCore [num] Run on CPU for publisher (default %d)\n" - " -subCore [num] Run on CPU for subscriber (default %d)\n" - " -userAppCore [num] Run on CPU for userApplication (default %d)\n" - " -pubMacAddress [name] Publisher Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -subMacAddress [name] Subscriber Mac address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " -qbvOffset [num] QBV offset value (default %d)\n" - " -disableSoTxtime Do not use SO_TXTIME\n" - " -enableCsvLog Experimental: To log the data in csv files. Support up to 1 million samples\n" - " -enableLatencyCsvLog Experimental: To compute and create RTT latency csv. Support up to 1 million samples\n" - " -enableconsolePrint Experimental: To print the data in console output. Support for higher cycle time\n" - " -enableXdpSubscribe Enable XDP feature for subscriber. XDP_COPY and XDP_FLAGS_SKB_MODE is used by default. Not recommended to be enabled along with blocking socket.\n" - " -xdpQueue [num] XDP queue value (default %d)\n" - " -xdpFlagDrvMode Use XDP in DRV mode\n" - " -xdpBindFlagZeroCopy Use Zero-Copy mode in XDP\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \ - DEFAULT_SUB_SCHED_PRIORITY, DEFAULT_USERAPPLICATION_SCHED_PRIORITY, \ - DEFAULT_PUB_CORE, DEFAULT_SUB_CORE, DEFAULT_USER_APP_CORE, \ - DEFAULT_PUBLISHING_MAC_ADDRESS, DEFAULT_SUBSCRIBING_MAC_ADDRESS, DEFAULT_QBV_OFFSET, DEFAULT_XDP_QUEUE); -} - -/** - * Main Server code - * ~~~~~~~~~~~~~~~~ - * - * The main function contains publisher and subscriber threads running in - * parallel. */ -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - char *interface = NULL; - UA_Int32 argInputs = 0; - UA_Int32 long_index = 0; - char *progname = NULL; - pthread_t userThreadID; - - /* Process the command line arguments */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - - static struct option long_options[] = { - {"interface", required_argument, 0, 'a'}, - {"cycleTimeInMsec", required_argument, 0, 'b'}, - {"socketPriority", required_argument, 0, 'c'}, - {"pubPriority", required_argument, 0, 'd'}, - {"subPriority", required_argument, 0, 'e'}, - {"userAppPriority", required_argument, 0, 'f'}, - {"pubCore", required_argument, 0, 'g'}, - {"subCore", required_argument, 0, 'h'}, - {"userAppCore", required_argument, 0, 'i'}, - {"pubMacAddress", required_argument, 0, 'j'}, - {"subMacAddress", required_argument, 0, 'k'}, - {"qbvOffset", required_argument, 0, 'l'}, - {"disableSoTxtime", no_argument, 0, 'm'}, - {"enableCsvLog", no_argument, 0, 'n'}, - {"enableLatencyCsvLog", no_argument, 0, 'o'}, - {"enableconsolePrint", no_argument, 0, 'p'}, - {"xdpQueue", required_argument, 0, 'r'}, - {"xdpFlagDrvMode", no_argument, 0, 's'}, - {"xdpBindFlagZeroCopy", no_argument, 0, 't'}, - {"enableXdpSubscribe", no_argument, 0, 'u'}, - {"help", no_argument, 0, 'v'}, - {0, 0, 0, 0 } - }; - - while((argInputs = getopt_long_only(argc, argv,"", long_options, &long_index)) != -1) { - switch(argInputs) { - case 'a': - interface = optarg; - break; - case 'b': - cycleTimeInMsec = atof(optarg); - break; - case 'c': - socketPriority = atoi(optarg); - break; - case 'd': - pubPriority = atoi(optarg); - break; - case 'e': - subPriority = atoi(optarg); - break; - case 'f': - userAppPriority = atoi(optarg); - break; - case 'g': - pubCore = atoi(optarg); - break; - case 'h': - subCore = atoi(optarg); - break; - case 'i': - userAppCore = atoi(optarg); - break; - case 'j': - pubMacAddress = optarg; - break; - case 'k': - subMacAddress = optarg; - break; - case 'l': - qbvOffset = atoi(optarg); - break; - case 'm': - disableSoTxtime = false; - break; - case 'n': - enableCsvLog = true; - break; - case 'o': - enableLatencyCsvLog = true; - break; - case 'p': - consolePrint = true; - break; - case 'r': - xdpQueue = (UA_UInt32)atoi(optarg); - break; - case 's': - xdpFlag = XDP_FLAGS_DRV_MODE; - break; - case 't': - xdpBindFlag = XDP_ZEROCOPY; - break; - case 'u': - enableXdpSubscribe = true; - break; - case 'v': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if(!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run"); - usage(progname); - UA_Server_delete(server); - return 0; - } - - if(cycleTimeInMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeInMsec); - usage(progname); - return -1; - } - - if(xdpFlag == XDP_FLAGS_DRV_MODE || xdpBindFlag == XDP_ZEROCOPY) { - if(enableXdpSubscribe == false) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Flag enableXdpSubscribe is false, running application without XDP"); - } - - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); -#ifdef UA_ENABLE_PUBSUB_ENCRYPTION -#if defined(PUBLISHER) && defined(SUBSCRIBER) - /* Instantiate the PubSub SecurityPolicy */ - config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) - UA_calloc(2, sizeof(UA_PubSubSecurityPolicy)); - config->pubSubConfig.securityPoliciesSize = 2; -#else - config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*) - UA_malloc(sizeof(UA_PubSubSecurityPolicy)); - config->pubSubConfig.securityPoliciesSize = 1; -#endif -#endif - -#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(PUBLISHER) - UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[0], - config->logging); -#endif - -#if defined(PUBLISHER) - UA_NetworkAddressUrlDataType networkAddressUrlPub; -#endif - -#if defined(SUBSCRIBER) - UA_NetworkAddressUrlDataType networkAddressUrlSub; -#endif - -#if defined(PUBLISHER) - networkAddressUrlPub.networkInterface = UA_STRING(interface); - networkAddressUrlPub.url = UA_STRING(pubMacAddress); -#endif -#if defined(SUBSCRIBER) - networkAddressUrlSub.networkInterface = UA_STRING(interface); - networkAddressUrlSub.url = UA_STRING(subMacAddress); -#endif - - if(enableCsvLog) { -#if defined(PUBLISHER) - fpPublisher = fopen(filePublishedData, "w"); -#endif - -#if defined(SUBSCRIBER) - fpSubscriber = fopen(fileSubscribedData, "w"); -#endif - } - - /* Create variable nodes for publisher and subscriber in address space */ - addServerNodes(server); - -#if defined(PUBLISHER) - addPubSubConnection(server, &networkAddressUrlPub); - addPublishedDataSet(server); - _addDataSetField(server); - addWriterGroup(server); - addDataSetWriter(server); - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); -#endif - -#if defined(UA_ENABLE_PUBSUB_ENCRYPTION) && defined(SUBSCRIBER) - UA_PubSubSecurityPolicy_Aes128Ctr(&config->pubSubConfig.securityPolicies[1], - config->logging); -#endif - -#if defined(SUBSCRIBER) - addPubSubConnectionSubscriber(server, &networkAddressUrlSub); - addReaderGroup(server); - addDataSetReader(server); - UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif - - serverConfigStruct *serverConfig; - serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct)); - serverConfig->ServerRun = server; -#if defined(PUBLISHER) || defined(SUBSCRIBER) - char threadNameUserApplication[22] = "UserApplicationPubSub"; - userThreadID = threadCreation((UA_Int16)userAppPriority, (size_t)userAppCore, - userApplicationPubSub, threadNameUserApplication, serverConfig); -#endif - - retval |= UA_Server_run(server, &runningServer); -#if defined(SUBSCRIBER) - UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif -#if defined(PUBLISHER) || defined(SUBSCRIBER) - returnValue = pthread_join(userThreadID, NULL); - if(returnValue != 0) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "\nPthread Join Failed for User thread:%d\n", returnValue); -#endif - - if(enableCsvLog) { -#if defined(PUBLISHER) - /* Write the published data in the publisher_T1.csv file */ - size_t pubLoopVariable = 0; - for(pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - fprintf(fpPublisher, "%lu,%lu.%09lu\n", - (long unsigned)publishCounterValue[pubLoopVariable], - (long unsigned)publishTimestamp[pubLoopVariable].tv_sec, - (long unsigned)publishTimestamp[pubLoopVariable].tv_nsec); - } -#endif -#if defined(SUBSCRIBER) - /* Write the subscribed data in the subscriber_T8.csv file */ - size_t subLoopVariable = 0; - for(subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%lu,%lu.%09lu\n", - (long unsigned)subscribeCounterValue[subLoopVariable], - (long unsigned)subscribeTimestamp[subLoopVariable].tv_sec, - (long unsigned)subscribeTimestamp[subLoopVariable].tv_nsec); - } -#endif - } - - if(enableLatencyCsvLog) { -#if defined (PUBLISHER) && defined(SUBSCRIBER) - char *latencyCsvName = LATENCY_CSV_FILE_NAME; - computeLatencyAndGenerateCsv(latencyCsvName); -#endif - } - -#if defined(PUBLISHER) || defined(SUBSCRIBER) - removeServerNodes(server); - UA_Server_delete(server); - UA_free(serverConfig); -#endif - -#if defined(PUBLISHER) - UA_free(runningPub); - UA_free(pubCounterData); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(pubDataValueRT); - UA_free(runningPubDataValueRT); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedDataValueRT[iterator]); - if(enableCsvLog) - fclose(fpPublisher); -#endif -#if defined(SUBSCRIBER) - UA_free(runningSub); - UA_free(subCounterData); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(subDataValueRT); - UA_free(runningSubDataValueRT); - for(UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedDataValueRT[iterator]); - if(enableCsvLog) - fclose(fpSubscriber); -#endif - - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c b/examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c deleted file mode 100644 index fea3091a8..000000000 --- a/examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c +++ /dev/null @@ -1,1505 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -/** - * .. _pubsub-tutorial: - * - * Realtime Publish Example - * ------------------------ - * - * This tutorial shows publishing and subscribing information in Realtime. - * This example has both Publisher and Subscriber(used as threads, running in different core), the Publisher thread publishes counterdata - * (an incremental data), that is subscribed by the Subscriber thread of pubsub_TSN_loopback.c example. The Publisher thread of - * pusbub_TSN_loopback.c publishes the received counterdata, which is subscribed by the Subscriber thread of this example. - * Thus a round-trip of counterdata is achieved. User application function of publisher and subscriber is used to collect the publisher and - * subscriber data. - * - * Another additional feature called the Blocking Socket is employed in the Subscriber thread. When using Blocking Socket, - * the Subscriber thread remains in "blocking mode" until a message is received from every wake up time of the thread. In other words, - * the timeout is overwritten and the thread continuously waits for the message from every wake up time of the thread. - * Once the message is received, the Subscriber thread updates the value in the Information Model, sleeps up to wake up time and - * again waits for the next message. This process is repeated until the application is terminated. - * - * Run step of the example is as mentioned below: - * - * ./bin/examples/pubsub_TSN_publisher_multiple_thread -interface -operBaseTime -monotonicOffset - * - * For more options, run ./bin/examples/pubsub_TSN_publisher_multiple_thread -h - */ - -/** - * Trace point setup - * - * +--------------+ +----------------+ - * T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4 - * | | Application | ^ | | Application | ^ - * | +--------------+ | | +----------------+ | - * User | | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * ----------|--------------|------------------------|----------------|------- - * | | Node 1 | | | | Node 2 | | - * Kernel| | | | | | | | - * Space | | | | | | | | - * | | | | | | | | - * v +--------------+ | v +----------------+ | - * T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3 - * | +--------------+ +----------------+ ^ - * | | - * ---------------------------------------------------------------- - */ - -#define _GNU_SOURCE -//#include "open62541.h" -#include -#include -#include -#include -#include -#include -#include - -/* For thread operations */ -#include - -#include -#include -#include -#include -#include -#include - -#include "ua_pubsub.h" - -/*to find load of each thread - * ps -L -o pid,pri,%cpu -C pubsub_TSN_publisher_multiple_thread */ - -/* Configurable Parameters */ -//If you disable the below macro then two way communication then only publisher will be active -#define TWO_WAY_COMMUNICATION -/* Cycle time in milliseconds */ -#define DEFAULT_CYCLE_TIME 0.25 -/* Qbv offset */ -#define DEFAULT_QBV_OFFSET 125 -#define DEFAULT_SOCKET_PRIORITY 7 -#define PUBLISHER_ID 2234 -#define WRITER_GROUP_ID 101 -#define DATA_SET_WRITER_ID 62541 -#define DEFAULT_PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.7" -#define DEFAULT_PUBLISHER_MULTICAST_ADDRESS "opc.udp://224.0.0.22:4840/" -#define PUBLISHER_ID_SUB 2235 -#define WRITER_GROUP_ID_SUB 100 -#define DATA_SET_WRITER_ID_SUB 62541 - -#define REPEATED_NODECOUNTS 2 // Default to publish 64 bytes -#define PORT_NUMBER 62541 -#define DEFAULT_PUBAPP_THREAD_PRIORITY 85 -#define DEFAULT_PUBAPP_THREAD_CORE 1 - -#define DEFAULT_SUBAPP_THREAD_PRIORITY 90 -#define DEFAULT_SUBAPP_THREAD_CORE 0 -#define DEFAULT_SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01:8.7" -#define DEFAULT_SUBSCRIBER_MULTICAST_ADDRESS "opc.udp://224.0.0.32:4840/" - -/* Non-Configurable Parameters */ -/* Milli sec and sec conversion to nano sec */ -#define MILLI_SECONDS 1000000 -#if defined(__arm__) -#define SECONDS 1e9 -#else -#define SECONDS 1000000000 -#endif -#define SECONDS_SLEEP 5 - -#define MAX_MEASUREMENTS 100000 -#define SECONDS_INCREMENT 1 -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif -#define CLOCKID CLOCK_MONOTONIC -#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define UDP_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp" - - -/* If the Hardcoded publisher/subscriber MAC addresses need to be changed, - * change PUBLISHING_MAC_ADDRESS and SUBSCRIBING_MAC_ADDRESS - */ - -/* Set server running as true */ -UA_Boolean runningServer = UA_TRUE; - -char* pubUri = DEFAULT_PUBLISHING_MAC_ADDRESS; -char* subUri = DEFAULT_SUBSCRIBING_MAC_ADDRESS; -static UA_Double cycleTimeInMsec = DEFAULT_CYCLE_TIME; -static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY; -static UA_Int32 pubAppPriority = DEFAULT_PUBAPP_THREAD_PRIORITY; -static UA_Int32 subAppPriority = DEFAULT_SUBAPP_THREAD_PRIORITY; -static UA_Int32 pubAppCore = DEFAULT_PUBAPP_THREAD_CORE; -static UA_Int32 subAppCore = DEFAULT_SUBAPP_THREAD_CORE; -static UA_Int32 qbvOffset = DEFAULT_QBV_OFFSET; -static UA_Boolean disableSoTxtime = UA_TRUE; -static UA_Boolean enableCsvLog = UA_FALSE; -static UA_Boolean consolePrint = UA_FALSE; -static UA_Boolean signalTerm = UA_FALSE; - -/* Variables corresponding to PubSub connection creation, - * published data set and writer group */ -UA_NodeId connectionIdent; -UA_NodeId publishedDataSetIdent; -UA_NodeId writerGroupIdent; -UA_NodeId pubNodeID; -UA_NodeId runningPubStatusNodeID; -UA_NodeId pubRepeatedCountNodeID; - -/* Variables for counter data handling in address space */ -UA_UInt64 *pubCounterData = NULL; -UA_DataValue *pubDataValueRT = NULL; -UA_Boolean *runningPub = NULL; -UA_DataValue *runningPubDataValueRT = NULL; -UA_UInt64 *repeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *repeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; - -#ifdef TWO_WAY_COMMUNICATION -UA_NodeId subNodeID; -UA_NodeId subRepeatedCountNodeID; -UA_NodeId runningSubStatusNodeID; -UA_UInt64 *subCounterData = NULL; -UA_DataValue *subDataValueRT = NULL; -UA_Boolean *runningSub = NULL; -UA_DataValue *runningSubDataValueRT = NULL; -UA_UInt64 *subRepeatedCounterData[REPEATED_NODECOUNTS] = {NULL}; -UA_DataValue *subRepeatedDataValueRT[REPEATED_NODECOUNTS] = {NULL}; -#endif -/** - * **CSV file handling** - * - * csv files are written for pubSubApp thread. - * csv files include the counterdata that is being either Published or Subscribed - * along with the timestamp. These csv files can be used to compute latency for following - * combinations of Tracepoints, T1-T4 and T1-T8. - * - * T1-T8 - Gives the Round-trip time of a counterdata, as the value published by the Publisher thread - * in pubsub_TSN_publisher_multiple_thread.c example is subscribed by the pubSubApp thread in pubsub_TSN_loopback_single_thread.c - * example and is published back to the pubsub_TSN_publisher_multiple_thread.c example - */ -/* File to store the data and timestamps for different traffic */ -FILE *fpPublisher; -char *filePublishedData = "publisher_T1.csv"; -/* Array to store published counter data */ -UA_UInt64 publishCounterValue[MAX_MEASUREMENTS]; -size_t measurementsPublisher = 0; -/* Array to store timestamp */ -struct timespec publishTimestamp[MAX_MEASUREMENTS]; - -struct timespec dataModificationTime; - -#ifdef TWO_WAY_COMMUNICATION -/* File to store the data and timestamps for different traffic */ -FILE *fpSubscriber; -char *fileSubscribedData = "subscriber_T8.csv"; -/* Array to store subscribed counter data */ -UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS]; -size_t measurementsSubscriber = 0; -/* Array to store timestamp */ -struct timespec subscribeTimestamp[MAX_MEASUREMENTS]; - -/* Variable for PubSub connection creation */ -UA_NodeId connectionIdentSubscriber; -struct timespec dataReceiveTime; -UA_UInt64 previousSubCounterData; -UA_NodeId readerGroupIdentifier; -UA_NodeId readerIdentifier; -UA_DataSetReaderConfig readerConfig; -#endif - -/* Structure to define thread parameters */ -typedef struct { -UA_Server* server; -void* pubData; -void* subData; -UA_ServerCallback pubCallback; -UA_ServerCallback subCallback; -UA_Duration interval_ms; -UA_UInt64 operBaseTime; -UA_UInt64 monotonicOffset; -UA_UInt64 packetLossCount; -} threadArgPubSub; - -threadArgPubSub *threadArgPubSub1; - -/* Pub application thread routine */ -void *pubApp(void *arg); -/* For adding nodes in the server information model */ -static void addServerNodes(UA_Server *server); -/* For deleting the nodes created */ -static void removeServerNodes(UA_Server *server); -/* To create multi-threads */ -static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread) (void *), - char *applicationName, void *serverConfig); -void userApplicationPublisher(UA_UInt64 monotonicOffsetValue); -#ifdef TWO_WAY_COMMUNICATION -/* Sub application thread routine */ -void *subApp(void *arg); -void userApplicationSubscriber(UA_UInt64 monotonicOffsetValue); -#endif - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - signalTerm = UA_TRUE; -} - -/** - * **Nanosecond field handling** - * - * Nanosecond field in timespec is checked for overflowing and one second - * is added to seconds field and nanosecond field is set to zero -*/ -static void nanoSecondFieldConversion(struct timespec *timeSpecValue) { - /* Check if ns field is greater than '1 ns less than 1sec' */ - while (timeSpecValue->tv_nsec > (SECONDS -1)) { - /* Move to next second and remove it from ns field */ - timeSpecValue->tv_sec += SECONDS_INCREMENT; - timeSpecValue->tv_nsec -= (__syscall_slong_t)SECONDS; - } - -} - -/** - * **Custom callback handling** - * - * Custom callback thread handling overwrites the default timer based - * callback function with the custom (user-specified) callback interval. */ -/* Add a callback for cyclic repetition */ -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - - /* Check the writer group identifier and create the thread accordingly */ - if(UA_NodeId_equal(&identifier, &writerGroupIdent)) { - threadArgPubSub1->pubData = data; - threadArgPubSub1->pubCallback = callback; - threadArgPubSub1->interval_ms = interval_ms; - } - else { - threadArgPubSub1->subData = data; - threadArgPubSub1->subCallback = callback; - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - /* Callback interval need not be modified as it is thread based implementation. - * The thread uses nanosleep for calculating cycle time and modification in - * nanosleep value changes cycle time */ - return UA_STATUSCODE_GOOD; -} - - -/* Remove the callback added for cyclic repetition */ -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { -/* ToDo: Handle thread id */ -} - -/** - * **External data source handling** - * - * If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data){ - //node values are updated by using variables in the memory - //UA_Server_write is not used for updating node values. - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeid, - void *nodeContext, const UA_NumericRange *range){ - //allow read without any preparation - return UA_STATUSCODE_GOOD; -} - -#ifdef TWO_WAY_COMMUNICATION -/** - * **Subscriber** - * - * Create connection, readergroup, datasetreader, subscribedvariables for the Subscriber thread. - */ -static void -addPubSubConnectionSubscriber(UA_Server *server, UA_String *transportProfile, - UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){ - UA_StatusCode retval = UA_STATUSCODE_GOOD; - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Subscriber Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber; - - connectionConfig.transportProfileUri = *transportProfile; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = (UA_UInt16) UA_UInt32_random(); - retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber); - if (retval == UA_STATUSCODE_GOOD) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!"); -} - -/* Add ReaderGroup to the created connection */ -static void -addReaderGroup(UA_Server *server) { - if(server == NULL) - return; - - UA_ReaderGroupConfig readerGroupConfig; - memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup"); - readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - - UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig, - &readerGroupIdentifier); - UA_Server_enableReaderGroup(server, readerGroupIdentifier); -} - -/* Set SubscribedDataSet type to TargetVariables data type - * Add subscribedvariables to the DataSetReader */ -static void addSubscribedVariables (UA_Server *server) { - UA_Int32 iterator = 0; - UA_Int32 iteratorRepeatedCount = 0; - - if(server == NULL) { - return; - } - - UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*) UA_calloc((REPEATED_NODECOUNTS + 2), sizeof(UA_FieldTargetVariable)); - if(!targetVars) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "FieldTargetVariable - Bad out of memory"); - return; - } - - runningSub = UA_Boolean_new(); - if(!runningSub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningsub - Bad out of memory"); - UA_free(targetVars); - return; - } - - *runningSub = UA_TRUE; - runningSubDataValueRT = UA_DataValue_new(); - if(!runningSubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningsubDatavalue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&runningSubDataValueRT->value, runningSub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningSubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningSubvalueBackend; - runningSubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningSubvalueBackend.backend.external.value = &runningSubDataValueRT; - runningSubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningSubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)30000), runningSubvalueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - iterator++; - /* For creating Targetvariable */ - for (iterator = 1, iteratorRepeatedCount = 0; iterator <= REPEATED_NODECOUNTS; iterator++, iteratorRepeatedCount++) - { - subRepeatedCounterData[iteratorRepeatedCount] = UA_UInt64_new(); - if(!subRepeatedCounterData[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeRepeatedCounterData - Bad out of memory"); - UA_free(targetVars); - return; - } - - *subRepeatedCounterData[iteratorRepeatedCount] = 0; - subRepeatedDataValueRT[iteratorRepeatedCount] = UA_DataValue_new(); - if(!subRepeatedDataValueRT[iteratorRepeatedCount]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeRepeatedCounterDataValue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&subRepeatedDataValueRT[iteratorRepeatedCount]->value, subRepeatedCounterData[iteratorRepeatedCount], &UA_TYPES[UA_TYPES_UINT64]); - subRepeatedDataValueRT[iteratorRepeatedCount]->hasValue = UA_TRUE; - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subRepeatedDataValueRT[iteratorRepeatedCount]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount+50000), valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iteratorRepeatedCount + 50000); - } - - subCounterData = UA_UInt64_new(); - if(!subCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeCounterData - Bad out of memory"); - UA_free(targetVars); - return; - } - - *subCounterData = 0; - subDataValueRT = UA_DataValue_new(); - if(!subDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "SubscribeDataValue - Bad out of memory"); - UA_free(targetVars); - return; - } - - UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]); - subDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &subDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, subNodeID, valueBackend); - - UA_FieldTargetDataType_init(&targetVars[iterator].targetVariable); - targetVars[iterator].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[iterator].targetVariable.targetNodeId = subNodeID; - - /* Set the subscribed data to TargetVariable type */ - readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars; - readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = REPEATED_NODECOUNTS + 2; -} - -/* Add DataSetReader to the ReaderGroup */ -static void -addDataSetReader(UA_Server *server) { - UA_Int32 iterator = 0; - if(server == NULL) { - return; - } - - memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); - readerConfig.name = UA_STRING("DataSet Reader"); - UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB; - readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16]; - readerConfig.publisherId.data = &publisherIdentifier; - readerConfig.writerGroupId = WRITER_GROUP_ID_SUB; - readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB; - - readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE]; - UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new(); - dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage; - - /* Setting up Meta data configuration in DataSetReader */ - UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData; - UA_DataSetMetaDataType_init (pMetaData); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = REPEATED_NODECOUNTS + 2; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* Boolean DataType */ - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_BOOLEAN].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_BOOLEAN; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - iterator++; - for (iterator = 1; iterator <= REPEATED_NODECOUNTS; iterator++) - { - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - } - - /* Unsigned Integer DataType */ - UA_FieldMetaData_init (&pMetaData->fields[iterator]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId, - &pMetaData->fields[iterator].dataType); - pMetaData->fields[iterator].builtInType = UA_NS0ID_UINT64; - pMetaData->fields[iterator].valueRank = -1; /* scalar */ - - /* Setup Target Variables in DSR config */ - addSubscribedVariables(server); - - /* Setting up Meta data configuration in DataSetReader */ - UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, - &readerIdentifier); - - UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables); - UA_free(readerConfig.dataSetMetaData.fields); - UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage); -} -#endif - -/** - * **Publisher** - * - * Create connection, writergroup, datasetwriter and publisheddataset for Publisher thread. - */ -static void -addPubSubConnection(UA_Server *server, UA_String *transportProfile, - UA_NetworkAddressUrlDataType *networkAddressUrlPub){ - /* Details about the connection configuration and handling are located - * in the pubsub connection tutorial */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("Publisher Connection"); - connectionConfig.enabled = UA_TRUE; - UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub; - connectionConfig.transportProfileUri = *transportProfile; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_Variant_setScalar(&connectionOptions[0].value, &socketPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Variant_setScalar(&connectionOptions[1].value, &disableSoTxtime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); -} - -/* PublishedDataset handling */ -static void -addPublishedDataSet(UA_Server *server) { - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); -} - -/* DataSetField handling */ -static void -_addDataSetField(UA_Server *server) { - /* Add a field to the previous created PublishedDataSet */ - UA_NodeId dataSetFieldIdent1; - UA_DataSetFieldConfig dataSetFieldConfig; -#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS - staticValueSource = UA_DataValue_new(); -#endif - - UA_NodeId dataSetFieldIdentRunning; - UA_DataSetFieldConfig dsfConfigPubStatus; - memset(&dsfConfigPubStatus, 0, sizeof(UA_DataSetFieldConfig)); - - runningPub = UA_Boolean_new(); - if(!runningPub) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPub - Bad out of memory"); - return; - } - - *runningPub = UA_TRUE; - runningPubDataValueRT = UA_DataValue_new(); - if(!runningPubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "runningPubDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&runningPubDataValueRT->value, runningPub, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningPubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend runningPubvalueBackend; - runningPubvalueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - runningPubvalueBackend.backend.external.value = &runningPubDataValueRT; - runningPubvalueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - runningPubvalueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)20000), runningPubvalueBackend); - - /* setup RT DataSetField config */ - dsfConfigPubStatus.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfigPubStatus.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfigPubStatus, &dataSetFieldIdentRunning); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - - repeatedCounterData[iterator] = UA_UInt64_new(); - if(!repeatedCounterData[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounter - Bad out of memory"); - return; - } - - *repeatedCounterData[iterator] = 0; - repeatedDataValueRT[iterator] = UA_DataValue_new(); - if(!repeatedDataValueRT[iterator]) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishRepeatedCounterDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&repeatedDataValueRT[iterator]->value, repeatedCounterData[iterator], &UA_TYPES[UA_TYPES_UINT64]); - repeatedDataValueRT[iterator]->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &repeatedDataValueRT[iterator]; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000), valueBackend); - - /* setup RT DataSetField config */ - dataSetFieldConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dataSetFieldConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent1); - } - - UA_NodeId dataSetFieldIdent; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - - pubCounterData = UA_UInt64_new(); - if(!pubCounterData) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishCounter - Bad out of memory"); - return; - } - - *pubCounterData = 0; - pubDataValueRT = UA_DataValue_new(); - if(!pubDataValueRT) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "PublishDataValue - Bad out of memory"); - return; - } - - UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]); - pubDataValueRT->hasValue = UA_TRUE; - - /* Set the value backend of the above create node to 'external value source' */ - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &pubDataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback; - UA_Server_setVariableNode_valueBackend(server, pubNodeID, valueBackend); - - /* setup RT DataSetField config */ - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig.field.variable.publishParameters.publishedVariable = pubNodeID; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent); -} - -/* WriterGroup handling */ -static void -addWriterGroup(UA_Server *server) { - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = cycleTimeInMsec; - writerGroupConfig.enabled = UA_FALSE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.writerGroupId = WRITER_GROUP_ID; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - - writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; - writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; - /* The configuration flags for the messages are encapsulated inside the - * message- and transport settings extension objects. These extension - * objects are defined by the standard. e.g. - * UadpWriterGroupMessageDataType */ - UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new(); - /* Change message settings of writerGroup to send PublisherId, - * WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader - * of NetworkMessage */ - writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | - (UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage; - UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); - UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage); -} - -/* DataSetWriter handling */ -static void -addDataSetWriter(UA_Server *server) { - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); -} - -/** - * **Published data handling** - * - * The published data is updated in the array using this function - */ -static void -updateMeasurementsPublisher(struct timespec start_time, - UA_UInt64 counterValue, UA_UInt64 monotonicOffsetValue) { - if(measurementsPublisher >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Publisher: Maximum log measurements reached - Closing the application"); - signalTerm = UA_TRUE; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Pub:%"PRId64",%ld.%09ld\n", counterValue, start_time.tv_sec, start_time.tv_nsec); - - if (signalTerm != UA_TRUE){ - UA_UInt64 actualTimeValue = (UA_UInt64)((start_time.tv_sec * SECONDS) + start_time.tv_nsec) + monotonicOffsetValue; - publishTimestamp[measurementsPublisher].tv_sec = (__time_t)(actualTimeValue/(UA_UInt64)SECONDS); - publishTimestamp[measurementsPublisher].tv_nsec = (__syscall_slong_t)(actualTimeValue%(UA_UInt64)SECONDS); - publishCounterValue[measurementsPublisher] = counterValue; - measurementsPublisher++; - } -} - -/** - * userApplication function is used to increment the counterdata to be published by the Publisher and - * writes the updated counterdata in distinct csv files - **/ -void userApplicationPublisher(UA_UInt64 monotonicOffsetValue) { - - *pubCounterData = *pubCounterData + 1; - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - *repeatedCounterData[iterator] = *repeatedCounterData[iterator] + 1; - - clock_gettime(CLOCKID, &dataModificationTime); - - - if (enableCsvLog || consolePrint) { - if (*pubCounterData > 0) - updateMeasurementsPublisher(dataModificationTime, *pubCounterData, monotonicOffsetValue); - } - - /* *runningPub variable made false and send to the publisher application which is running in another node - which will close the application during blocking socket condition */ - if (signalTerm == UA_TRUE) { - *runningPub = UA_FALSE; - } - -} -#ifdef TWO_WAY_COMMUNICATION -/** - * **Subscribed data handling** - * - * The subscribed data is updated in the array using this function Subscribed data handling** - */ -static void -updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_UInt64 monotonicOffsetValue) { - if(measurementsSubscriber >= MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Subscriber: Maximum log measurements reached - Closing the application"); - signalTerm = UA_TRUE; - return; - } - - if(consolePrint) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"Sub:%"PRId64",%ld.%09ld\n", counterValue, receive_time.tv_sec, receive_time.tv_nsec); - - if (signalTerm != UA_TRUE){ - UA_UInt64 actualTimeValue = (UA_UInt64)((receive_time.tv_sec * SECONDS) + receive_time.tv_nsec) + monotonicOffsetValue; - subscribeTimestamp[measurementsSubscriber].tv_sec = (__time_t)(actualTimeValue/(UA_UInt64)SECONDS); - subscribeTimestamp[measurementsSubscriber].tv_nsec = (__syscall_slong_t)(actualTimeValue%(UA_UInt64)SECONDS); - subscribeCounterValue[measurementsSubscriber] = counterValue; - measurementsSubscriber++; - } -} - -/** - * userApplicationSubscriber function is used to read the data from Information Model for the Subscriber and - * writes the updated counterdata in distinct csv files - **/ -void userApplicationSubscriber(UA_UInt64 monotonicOffsetValue) { - - clock_gettime(CLOCKID, &dataReceiveTime); - - /* Check packet loss count */ - if ((*subCounterData > 0) && (*subCounterData != (previousSubCounterData + 1))) { - /* Check for duplicate packet */ - if (*subCounterData != previousSubCounterData) { - UA_UInt64 missedCount = *subCounterData - (previousSubCounterData + 1); - threadArgPubSub1->packetLossCount += missedCount; - } - } - - if (enableCsvLog || consolePrint) { - if (*subCounterData > 0) - updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, monotonicOffsetValue); - } - - previousSubCounterData = *subCounterData; - if (signalTerm == UA_TRUE) - *runningSub = UA_FALSE; - -} -#endif - -/** - * **Pub thread routine** - */ -void *pubApp(void *arg) { - UA_Server* server; - UA_ServerCallback pubCallback; - UA_WriterGroup* currentWriterGroup; - UA_UInt64 interval_ms; - struct timespec nextnanosleeptimePubApplication; - UA_UInt64 monotonicOffsetValue = 0; - - server = threadArgPubSub1->server; - currentWriterGroup = (UA_WriterGroup *)threadArgPubSub1->pubData; - pubCallback = threadArgPubSub1->pubCallback; - interval_ms = (UA_UInt64)(threadArgPubSub1->interval_ms * MILLI_SECONDS); - - //To synchronize the application along with gating cycle the below calculations are made - //Below calculations are done for monotonic clock - struct timespec currentTimeInTs; - UA_UInt64 addingValueToStartTime; - clock_gettime(CLOCKID, ¤tTimeInTs); - UA_UInt64 currentTimeInNs = (UA_UInt64)((currentTimeInTs.tv_sec * (SECONDS)) + currentTimeInTs.tv_nsec); - currentTimeInNs = currentTimeInNs + threadArgPubSub1->monotonicOffset; - UA_UInt64 timeToStart = currentTimeInNs + (SECONDS_SLEEP * (UA_UInt64)(SECONDS)); //Adding 5 seconds to start the cycle - if (threadArgPubSub1->operBaseTime != 0){ - UA_UInt64 moduloValueOfOperBaseTime = timeToStart % threadArgPubSub1->operBaseTime; - if(moduloValueOfOperBaseTime > interval_ms) - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime % interval_ms); - else - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime); - - timeToStart = timeToStart + addingValueToStartTime; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - else{ - timeToStart = timeToStart - timeToStart%interval_ms; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - - UA_UInt64 CycleStartTimeS = (UA_UInt64)(timeToStart / (UA_UInt64)(SECONDS)); - UA_UInt64 CycleStartTimeNs = (UA_UInt64)(timeToStart - (CycleStartTimeS * (UA_UInt64)(SECONDS))); - nextnanosleeptimePubApplication.tv_sec = (__time_t )(CycleStartTimeS); - nextnanosleeptimePubApplication.tv_nsec = (__syscall_slong_t)(CycleStartTimeNs); - nanoSecondFieldConversion(&nextnanosleeptimePubApplication); - monotonicOffsetValue = threadArgPubSub1->monotonicOffset; - - while (*runningPub) { - //Sleep for cycle time - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimePubApplication, NULL); - - //Increments the counterdata - userApplicationPublisher(monotonicOffsetValue); - - //ToDo:Handled only for without SO_TXTIME - //Call the publish callback to publish the data into the network - pubCallback(server, currentWriterGroup); - - //Calculate nextwakeup time - nextnanosleeptimePubApplication.tv_nsec += (__syscall_slong_t)(cycleTimeInMsec * MILLI_SECONDS); - nanoSecondFieldConversion(&nextnanosleeptimePubApplication); - } - -#ifndef TWO_WAY_COMMUNICATION - runningServer = UA_FALSE; -#endif - return (void*)NULL; -} - -#ifdef TWO_WAY_COMMUNICATION -/** - * **Sub thread routine** - */ -void *subApp(void *arg) { - UA_Server* server; - UA_ReaderGroup* currentReaderGroup; - UA_ServerCallback subCallback; - UA_UInt64 interval_ms; - struct timespec nextnanosleeptimeSubApplication; - UA_UInt64 monotonicOffsetValue = 0; - - server = threadArgPubSub1->server; - currentReaderGroup = (UA_ReaderGroup*)threadArgPubSub1->subData; - subCallback = threadArgPubSub1->subCallback; - interval_ms = (UA_UInt64)(threadArgPubSub1->interval_ms * MILLI_SECONDS); - - //To synchronize the application along with gating cycle the below calculations are made - //Below calculations are done for monotonic clock - struct timespec currentTimeInTs; - UA_UInt64 addingValueToStartTime; - clock_gettime(CLOCKID, ¤tTimeInTs); - UA_UInt64 currentTimeInNs = (UA_UInt64)((currentTimeInTs.tv_sec * (SECONDS)) + currentTimeInTs.tv_nsec); - currentTimeInNs = currentTimeInNs + threadArgPubSub1->monotonicOffset; - UA_UInt64 timeToStart = currentTimeInNs + (SECONDS_SLEEP * (UA_UInt64)(SECONDS)); //Adding 5 seconds to start the cycle - if (threadArgPubSub1->operBaseTime != 0){ - UA_UInt64 moduloValueOfOperBaseTime = timeToStart % threadArgPubSub1->operBaseTime; - if(moduloValueOfOperBaseTime > interval_ms) - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime % interval_ms); - else - addingValueToStartTime = interval_ms - (moduloValueOfOperBaseTime); - - timeToStart = timeToStart + addingValueToStartTime; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - else{ - timeToStart = timeToStart - timeToStart%interval_ms; - timeToStart = timeToStart - (threadArgPubSub1->monotonicOffset); - } - - UA_UInt64 CycleStartTimeS = (UA_UInt64)(timeToStart / (UA_UInt64)(SECONDS)); - UA_UInt64 CycleStartTimeNs = (UA_UInt64)(timeToStart - (CycleStartTimeS * (UA_UInt64)(SECONDS))); - nextnanosleeptimeSubApplication.tv_sec = (__time_t )(CycleStartTimeS); - nextnanosleeptimeSubApplication.tv_nsec = (__syscall_slong_t)(CycleStartTimeNs); - nanoSecondFieldConversion(&nextnanosleeptimeSubApplication); - clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSubApplication, NULL); - monotonicOffsetValue = threadArgPubSub1->monotonicOffset; - - while (*runningSub) { - //Call the subscriber callback to receive the data - //Subscriber called at last because during blocking socket condition - //Publisher cannot publlish packet - subCallback(server, currentReaderGroup); - - //Check whether there is a packet loss - userApplicationSubscriber(monotonicOffsetValue); - } - - sleep(1); - runningServer = UA_FALSE; - return (void*)NULL; -} -#endif -/** - * **Thread creation** - * - * The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly - * created thread. - */ - -static pthread_t threadCreation(UA_Int16 threadPriority, size_t coreAffinity, void *(*thread) (void *), char *applicationName, \ - void *serverConfig){ - /* Core affinity set */ - cpu_set_t cpuset; - pthread_t threadID; - struct sched_param schedParam; - UA_Int32 returnValue = 0; - UA_Int32 errorSetAffinity = 0; - /* Return the ID for thread */ - threadID = pthread_self(); - schedParam.sched_priority = threadPriority; - returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam); - if (returnValue != 0) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n"); - exit(1); - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\ - "\npthread_setschedparam:%s Thread priority is %d \n", \ - applicationName, schedParam.sched_priority); - CPU_ZERO(&cpuset); - CPU_SET(coreAffinity, &cpuset); - errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset); - if (errorSetAffinity) { - fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity)); - exit(1); - } - - returnValue = pthread_create(&threadID, NULL, thread, serverConfig); - if (returnValue != 0) - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName); - - if (CPU_ISSET(coreAffinity, &cpuset)) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %zu\n", applicationName, coreAffinity); - - return threadID; -} - -/** - * **Creation of nodes** - * - * The addServerNodes function is used to create the publisher and subscriber - * nodes. - */ -static void addServerNodes(UA_Server *server) { - UA_NodeId objectId; - UA_NodeId newNodeId; - UA_ObjectAttributes object = UA_ObjectAttributes_default; - object.displayName = UA_LOCALIZEDTEXT("en-US", "Counter Object"); - UA_Server_addObjectNode(server, UA_NODEID_NULL, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Counter Object"), UA_NODEID_NULL, - object, NULL, &objectId); - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - UA_UInt64 publishValue = 0; - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter"); - newNodeId = UA_NODEID_STRING(1, "PublisherCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher Counter"), - UA_NODEID_NULL, publisherAttr, NULL, &pubNodeID); -#ifdef TWO_WAY_COMMUNICATION - UA_VariableAttributes subscriberAttr = UA_VariableAttributes_default; - UA_UInt64 subscribeValue = 0; - subscriberAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - subscriberAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&subscriberAttr.value, &subscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - subscriberAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber Counter"); - newNodeId = UA_NODEID_STRING(1, "SubscriberCounter"); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber Counter"), - UA_NODEID_NULL, subscriberAttr, NULL, &subNodeID); -#endif - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodePub = UA_VariableAttributes_default; - UA_UInt64 repeatedPublishValue = 0; - repeatedNodePub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodePub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&repeatedNodePub.value, &repeatedPublishValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodePub.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+10000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Publisher RepeatedCounter"), - UA_NODEID_NULL, repeatedNodePub, NULL, &pubRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusPub = UA_VariableAttributes_default; - UA_Boolean runningPubStatus = 0; - runningStatusPub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusPub.value, &runningPubStatus, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusPub.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Pub"); - runningStatusPub.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)20000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Pub"), - UA_NODEID_NULL, runningStatusPub, NULL, &runningPubStatusNodeID); -#ifdef TWO_WAY_COMMUNICATION - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_VariableAttributes repeatedNodeSub = UA_VariableAttributes_default; - UA_DateTime repeatedSubscribeValue; - UA_Variant_setScalar(&repeatedNodeSub.value, &repeatedSubscribeValue, &UA_TYPES[UA_TYPES_UINT64]); - repeatedNodeSub.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - repeatedNodeSub.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - repeatedNodeSub.displayName = UA_LOCALIZEDTEXT("en-US", "Subscriber RepeatedCounter"); - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)iterator+50000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Subscriber RepeatedCounter"), - UA_NODEID_NULL, repeatedNodeSub, NULL, &subRepeatedCountNodeID); - } - UA_VariableAttributes runningStatusSubscriber = UA_VariableAttributes_default; - UA_Boolean runningSubStatusValue = 0; - runningStatusSubscriber.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Variant_setScalar(&runningStatusSubscriber.value, &runningSubStatusValue, &UA_TYPES[UA_TYPES_BOOLEAN]); - runningStatusSubscriber.displayName = UA_LOCALIZEDTEXT("en-US", "RunningStatus Sub"); - runningStatusSubscriber.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; - newNodeId = UA_NODEID_NUMERIC(1, (UA_UInt32)30000); - UA_Server_addVariableNode(server, newNodeId, objectId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "RunningStatus Sub"), - UA_NODEID_NULL, runningStatusSubscriber, NULL, &runningSubStatusNodeID); -#endif -} - -/** - * **Deletion of nodes** - * - * The removeServerNodes function is used to delete the publisher and subscriber - * nodes. - */ - -static void removeServerNodes(UA_Server *server) { - /* Delete the Publisher Counter Node*/ - UA_Server_deleteNode(server, pubNodeID, UA_TRUE); - UA_NodeId_clear(&pubNodeID); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_Server_deleteNode(server, pubRepeatedCountNodeID, UA_TRUE); - UA_NodeId_clear(&pubRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningPubStatusNodeID, UA_TRUE); - UA_NodeId_clear(&runningPubStatusNodeID); -#ifdef TWO_WAY_COMMUNICATION - UA_Server_deleteNode(server, subNodeID, UA_TRUE); - UA_NodeId_clear(&subNodeID); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - { - UA_Server_deleteNode(server, subRepeatedCountNodeID, UA_TRUE); - UA_NodeId_clear(&subRepeatedCountNodeID); - } - UA_Server_deleteNode(server, runningSubStatusNodeID, UA_TRUE); - UA_NodeId_clear(&runningSubStatusNodeID); -#endif -} - -/** - * **Usage function** - * - * The usage function gives the information to run the application. - * - * ./bin/examples/pubsub_TSN_loopback_single_thread -interface -operBaseTime -monotonicOffset - * - * For more options, use ./bin/examples/pubsub_TSN_loopback_single_thread -h. - */ - -static void usage(char *appname) -{ - fprintf(stderr, - "\n" - "usage: %s [options]\n" - "\n" - " -interface [name] Use network interface 'name'\n" - " -cycleTimeInMsec [num] Cycle time in milli seconds (default %lf)\n" - " -socketPriority [num] Set publisher SO_PRIORITY to (default %d)\n" - " -pubAppPriority [num] publisher and userApp thread priority value (default %d)\n" - " -subAppPriority [num] subscriber and userApp thread priority value (default %d)\n" - " -pubAppCore [num] Run on CPU for publisher+pubUserApplication thread (default %d)\n" - " -subAppCore [num] Run on CPU for subscriber+subUserApplication thread (default %d)\n" - " -pubUri [name] Publisher Mac address(default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " or multicast address(default %s)\n" - " -subUri [name] Subscriber Mac address or multicast address (default %s - where 8 is the VLAN ID and 3 is the PCP)\n" - " or multicast address(default %s) \n" - " -qbvOffset [num] QBV offset value (default %d)\n" - " -operBaseTime [location] Bastime file location\n" - " -monotonicOffset [location] Monotonic offset file location\n" - " -disableSoTxtime Do not use SO_TXTIME\n" - " -enableCsvLog Experimental: To log the data in csv files. Support up to 1 million samples\n" - " -enableconsolePrint Experimental: To print the data in console output. Support for higher cycle time\n" - "\n", - appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, \ - DEFAULT_PUBAPP_THREAD_PRIORITY, \ - DEFAULT_SUBAPP_THREAD_PRIORITY, \ - DEFAULT_PUBAPP_THREAD_CORE, \ - DEFAULT_SUBAPP_THREAD_CORE, \ - DEFAULT_PUBLISHING_MAC_ADDRESS, DEFAULT_PUBLISHER_MULTICAST_ADDRESS, \ - DEFAULT_SUBSCRIBING_MAC_ADDRESS, DEFAULT_SUBSCRIBER_MULTICAST_ADDRESS, DEFAULT_QBV_OFFSET); -} - -/** - * **Main Server code** - */ -int main(int argc, char **argv) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Int32 returnValue = 0; - UA_StatusCode retval = UA_STATUSCODE_GOOD; - char *interface = NULL; - UA_Int32 argInputs = 0; - UA_Int32 long_index = 0; - char *progname; - pthread_t pubAppThreadID; -#ifdef TWO_WAY_COMMUNICATION - pthread_t subAppThreadID; -#endif - char *operBaseTimeFileName = NULL; - char *monotonicOffsetFileName = NULL; - FILE *operBaseTimefile; - FILE *monotonicOffsetFile; - UA_String transportProfile; - - /* Process the command line arguments */ - progname = strrchr(argv[0], '/'); - progname = progname ? 1 + progname : argv[0]; - - static struct option long_options[] = { - {"interface", required_argument, 0, 'a'}, - {"cycleTimeInMsec", required_argument, 0, 'b'}, - {"socketPriority", required_argument, 0, 'c'}, - {"pubAppPriority", required_argument, 0, 'd'}, - {"subAppPriority", required_argument, 0, 'e'}, - {"pubAppCore", required_argument, 0, 'f'}, - {"subAppCore", required_argument, 0, 'g'}, - {"pubUri", required_argument, 0, 'h'}, - {"subUri", required_argument, 0, 'i'}, - {"qbvOffset", required_argument, 0, 'j'}, - {"operBaseTime", required_argument, 0, 'k'}, - {"monotonicOffset", required_argument, 0, 'l'}, - {"disableSoTxtime", no_argument, 0, 'm'}, - {"enableCsvLog", no_argument, 0, 'n'}, - {"enableconsolePrint", no_argument, 0, 'o'}, - {"help", no_argument, 0, 'q'}, - {0, 0, 0, 0 } - }; - - while ((argInputs = getopt_long_only(argc, argv,"", long_options, &long_index)) != -1) { - switch (argInputs) { - case 'a': - interface = optarg; - break; - case 'b': - cycleTimeInMsec = atof(optarg); - break; - case 'c': - socketPriority = atoi(optarg); - break; - case 'd': - pubAppPriority = atoi(optarg); - break; - case 'e': - subAppPriority = atoi(optarg); - break; - case 'f': - pubAppCore = atoi(optarg); - break; - case 'g': - subAppCore = atoi(optarg); - break; - case 'h': - pubUri = optarg; - break; - case 'i': - subUri = optarg; - break; - case 'j': - qbvOffset = atoi(optarg); - break; - case 'k': - operBaseTimeFileName = optarg; - break; - case 'l': - monotonicOffsetFileName = optarg; - break; - case 'm': - disableSoTxtime = UA_FALSE; - break; - case 'n': - enableCsvLog = UA_TRUE; - break; - case 'o': - consolePrint = UA_TRUE; - break; - case 'q': - usage(progname); - return -1; - case '?': - usage(progname); - return -1; - } - } - - if (!interface) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run"); - usage(progname); - return 0; - } - - if (cycleTimeInMsec < 0.125) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeInMsec); - usage(progname); - return -1; - } - -#ifdef TWO_WAY_COMMUNICATION - /* The subscriber thread runs in a while loop so while running this application without blocking socket option - * the running application should be run in the seperate core where no process running in it */ -#endif - - if (disableSoTxtime == UA_TRUE) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "With sotxtime handling not supported so the application will run without soTxtime"); - disableSoTxtime = UA_FALSE; - } - - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL); - - UA_NetworkAddressUrlDataType networkAddressUrlPub; - - //If you are running for UDP provide ip address as input. Connection creation - //Failed if we provide the interface name - networkAddressUrlPub.networkInterface = UA_STRING(interface); - networkAddressUrlPub.url = UA_STRING(pubUri); -#ifdef TWO_WAY_COMMUNICATION - UA_NetworkAddressUrlDataType networkAddressUrlSub; - networkAddressUrlSub.networkInterface = UA_STRING(interface); - networkAddressUrlSub.url = UA_STRING(subUri); -#endif - - transportProfile = UA_STRING(ETH_TRANSPORT_PROFILE); - - if (enableCsvLog) - fpPublisher = fopen(filePublishedData, "w"); -#ifdef TWO_WAY_COMMUNICATION - if (enableCsvLog) - fpSubscriber = fopen(fileSubscribedData, "w"); -#endif - - /* Initialize arguments required for the thread to run */ - threadArgPubSub1 = (threadArgPubSub *) UA_malloc(sizeof(threadArgPubSub)); - - /* Server is the new OPCUA model which has both publisher and subscriber configuration */ - /* add axis node and OPCUA pubsub client server counter nodes */ - addServerNodes(server); - - addPubSubConnection(server, &transportProfile, &networkAddressUrlPub); - addPublishedDataSet(server); - _addDataSetField(server); - addWriterGroup(server); - addDataSetWriter(server); - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); - -#ifdef TWO_WAY_COMMUNICATION - addPubSubConnectionSubscriber(server, &transportProfile, &networkAddressUrlSub); - addReaderGroup(server); - addDataSetReader(server); - UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif - threadArgPubSub1->server = server; - - if (operBaseTimeFileName != NULL) { - long double floatValueBaseTime; - operBaseTimefile = fopen(operBaseTimeFileName, "r"); - fscanf(operBaseTimefile,"%Lf", &floatValueBaseTime); - uint64_t operBaseTimeInNs = (uint64_t)(floatValueBaseTime * SECONDS); - threadArgPubSub1->operBaseTime = operBaseTimeInNs; - } - else - threadArgPubSub1->operBaseTime = 0; - - if (monotonicOffsetFileName != NULL) { - monotonicOffsetFile = fopen(monotonicOffsetFileName, "r"); - char fileParseBuffer[255]; - if (fgets(fileParseBuffer, sizeof(fileParseBuffer), monotonicOffsetFile) != NULL) { - UA_UInt64 monotonicOffsetValueSecondsField = 0; - UA_UInt64 monotonicOffsetValueNanoSecondsField = 0; - UA_UInt64 monotonicOffsetInNs = 0; - const char* monotonicOffsetValueSec = strtok(fileParseBuffer, " "); - if (monotonicOffsetValueSec != NULL) - monotonicOffsetValueSecondsField = (UA_UInt64)(atoll(monotonicOffsetValueSec)); - - const char* monotonicOffsetValueNSec = strtok(NULL, " "); - if (monotonicOffsetValueNSec != NULL) - monotonicOffsetValueNanoSecondsField = (UA_UInt64)(atoll(monotonicOffsetValueNSec)); - - monotonicOffsetInNs = (monotonicOffsetValueSecondsField * (UA_UInt64)(SECONDS)) + monotonicOffsetValueNanoSecondsField; - threadArgPubSub1->monotonicOffset = monotonicOffsetInNs; - } - else - threadArgPubSub1->monotonicOffset = 0; - } - else - threadArgPubSub1->monotonicOffset = 0; - - threadArgPubSub1->packetLossCount = 0; - - char threadNamePubApp[22] = "PubApp"; - pubAppThreadID = threadCreation((UA_Int16)pubAppPriority, (size_t)pubAppCore, pubApp, threadNamePubApp, NULL); -#ifdef TWO_WAY_COMMUNICATION - char threadNameSubApp[22] = "SubApp"; - subAppThreadID = threadCreation((UA_Int16)subAppPriority, (size_t)subAppCore, subApp, threadNameSubApp, NULL); -#endif - retval |= UA_Server_run(server, &runningServer); - -#ifdef TWO_WAY_COMMUNICATION - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nTotal Packet Loss Count of publisher application :%"PRIu64"\n", \ - threadArgPubSub1->packetLossCount); - UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier); -#endif - returnValue = pthread_join(pubAppThreadID, NULL); - if (returnValue != 0) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for pubApp thread:%d\n", returnValue); - -#ifdef TWO_WAY_COMMUNICATION - returnValue = pthread_join(subAppThreadID, NULL); - if (returnValue != 0) - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for subApp thread:%d\n", returnValue); -#endif - - if (enableCsvLog) { - /* Write the published data in the publisher_T1.csv file */ - size_t pubLoopVariable = 0; - for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher; - pubLoopVariable++) { - fprintf(fpPublisher, "%"PRId64",%ld.%09ld\n", - publishCounterValue[pubLoopVariable], - publishTimestamp[pubLoopVariable].tv_sec, - publishTimestamp[pubLoopVariable].tv_nsec); - } -#ifdef TWO_WAY_COMMUNICATION - /* Write the subscribed data in the subscriber_T8.csv file */ - size_t subLoopVariable = 0; - for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber; - subLoopVariable++) { - fprintf(fpSubscriber, "%"PRId64",%ld.%09ld\n", - subscribeCounterValue[subLoopVariable], - subscribeTimestamp[subLoopVariable].tv_sec, - subscribeTimestamp[subLoopVariable].tv_nsec); - } -#endif - } - removeServerNodes(server); - UA_Server_delete(server); - if (operBaseTimeFileName != NULL) - fclose(operBaseTimefile); - - if (monotonicOffsetFileName != NULL) - fclose(monotonicOffsetFile); - - UA_free(runningPub); - UA_free(pubCounterData); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedCounterData[iterator]); - - /* Free external data source */ - UA_free(pubDataValueRT); - UA_free(runningPubDataValueRT); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(repeatedDataValueRT[iterator]); - - if (enableCsvLog) - fclose(fpPublisher); - -#ifdef TWO_WAY_COMMUNICATION - UA_free(runningSub); - UA_free(subCounterData); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedCounterData[iterator]); - /* Free external data source */ - UA_free(subDataValueRT); - UA_free(runningSubDataValueRT); - for (UA_Int32 iterator = 0; iterator < REPEATED_NODECOUNTS; iterator++) - UA_free(subRepeatedDataValueRT[iterator]); - UA_free(threadArgPubSub1); - if (enableCsvLog) - fclose(fpSubscriber); -#endif - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/pubsub_interrupt_publish.c b/examples/pubsub_realtime/attic/pubsub_interrupt_publish.c deleted file mode 100644 index d1ae591fe..000000000 --- a/examples/pubsub_realtime/attic/pubsub_interrupt_publish.c +++ /dev/null @@ -1,380 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. - * - * Copyright 2018-2019 (c) Kalycito Infotech - * Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner) - * Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer) - * Copyright (c) 2020 Wind River Systems, Inc. - */ - -#if __STDC_VERSION__ >= 199901L -#define _XOPEN_SOURCE 600 -#else -#define _XOPEN_SOURCE 500 -#endif /* __STDC_VERSION__ */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define ETH_PUBLISH_ADDRESS "opc.eth://0a-00-27-00-00-08" -#define ETH_INTERFACE "enp0s8" -#define MAX_MEASUREMENTS 10000 -#define MILLI_AS_NANO_SECONDS (1000 * 1000) -#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000) -#define CLOCKID CLOCK_MONOTONIC_RAW -#define SIG SIGUSR1 -#define PUB_INTERVAL 0.25 /* Publish interval in milliseconds */ -#define DATA_SET_WRITER_ID 62541 -#define MEASUREMENT_OUTPUT "publisher_measurement.csv" - -/* The RT level of the publisher */ -/* possible options: PUBSUB_CONFIG_FASTPATH_NONE, PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS, PUBSUB_CONFIG_FASTPATH_STATIC_VALUES */ -#define PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS - -UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}}; -UA_Int64 pubIntervalNs; -UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */ -UA_Server *pubServer; -UA_Boolean running = true; -void *pubData; -timer_t pubEventTimer; -struct sigevent pubEvent; -struct sigaction signalAction; - -#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES) -UA_DataValue *staticValueSource = NULL; -#endif - -/* Arrays to store measurement data */ -UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1]; -struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1]; -struct timespec cycleStartDelay[MAX_MEASUREMENTS+1]; -struct timespec cycleDuration[MAX_MEASUREMENTS+1]; -size_t publisherMeasurementsCounter = 0; - -/* The value to published */ -static UA_UInt64 publishValue = 62541; - -static UA_StatusCode -readPublishValue(UA_Server *server, - const UA_NodeId *sessionId, void *sessionContext, - const UA_NodeId *nodeId, void *nodeContext, - UA_Boolean sourceTimeStamp, const UA_NumericRange *range, - UA_DataValue *dataValue) { - UA_Variant_setScalarCopy(&dataValue->value, &publishValue, - &UA_TYPES[UA_TYPES_UINT64]); - dataValue->hasValue = true; - return UA_STATUSCODE_GOOD; -} - -static void -timespec_diff(struct timespec *start, struct timespec *stop, - struct timespec *result) { - if((stop->tv_nsec - start->tv_nsec) < 0) { - result->tv_sec = stop->tv_sec - start->tv_sec - 1; - result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000; - } else { - result->tv_sec = stop->tv_sec - start->tv_sec; - result->tv_nsec = stop->tv_nsec - start->tv_nsec; - } -} - -/* Used to adjust the nanosecond > 1s field value */ -static void -nanoSecondFieldConversion(struct timespec *timeSpecValue) { - while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) { - timeSpecValue->tv_sec += 1; - timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS; - } -} - -/* Signal handler */ -static void -publishInterrupt(int sig, siginfo_t* si, void* uc) { - if(si->si_value.sival_ptr != &pubEventTimer) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal"); - return; - } - - /* Execute the publish callback in the interrupt */ - struct timespec begin, end; - clock_gettime(CLOCKID, &begin); - pubCallback(pubServer, pubData); - clock_gettime(CLOCKID, &end); - - if(publisherMeasurementsCounter >= MAX_MEASUREMENTS) - return; - - /* Save current configured publish interval */ - currentPublishCycleTime[publisherMeasurementsCounter] = (UA_Int32)pubIntervalNs; - - /* Save the difference to the calculated time */ - timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter], - &begin, &cycleStartDelay[publisherMeasurementsCounter]); - - /* Save the duration of the publish callback */ - timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]); - - publisherMeasurementsCounter++; - - /* Save the calculated starting time for the next cycle */ - calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec = - calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_nsec + pubIntervalNs; - calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec = - calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_sec; - nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]); - - /* Write the pubsub measurement data */ - if(publisherMeasurementsCounter == MAX_MEASUREMENTS) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Logging the measurements to %s", MEASUREMENT_OUTPUT); - - FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w"); - for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) { - fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n", - i, - currentPublishCycleTime[i], - calculatedCycleStartTime[i].tv_sec, - calculatedCycleStartTime[i].tv_nsec, - cycleStartDelay[i].tv_sec, - cycleStartDelay[i].tv_nsec, - cycleDuration[i].tv_sec, - cycleDuration[i].tv_nsec); - } - fclose(fpPublisher); - } -} - -/* The following three methods are originally defined in - * /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to - * use system interrupts instead if time-triggered callbacks in the OPC UA - * server control flow. */ - -static UA_StatusCode -addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - if(pubCallback) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "At most one publisher can be registered for interrupt callbacks"); - return UA_STATUSCODE_BADINTERNALERROR; - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Adding a publisher with a cycle time of %lf milliseconds", interval_ms); - - /* Set global values for the publish callback */ - int resultTimerCreate = 0; - pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS); - - /* Handle the signal */ - memset(&signalAction, 0, sizeof(signalAction)); - signalAction.sa_flags = SA_SIGINFO; - signalAction.sa_sigaction = publishInterrupt; - sigemptyset(&signalAction.sa_mask); - sigaction(SIG, &signalAction, NULL); - - /* Create the timer */ - memset(&pubEventTimer, 0, sizeof(pubEventTimer)); - pubEvent.sigev_notify = SIGEV_SIGNAL; - pubEvent.sigev_signo = SIG; - pubEvent.sigev_value.sival_ptr = &pubEventTimer; - resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer); - if(resultTimerCreate != 0) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Failed to create a system event with code %s", - strerror(errno)); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Arm the timer */ - struct itimerspec timerspec; - timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS)); - timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS); - timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS)); - timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS); - resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL); - if(resultTimerCreate != 0) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Failed to arm the system timer with code %i", resultTimerCreate); - timer_delete(pubEventTimer); - return UA_STATUSCODE_BADINTERNALERROR; - } - - /* Start taking measurements */ - publisherMeasurementsCounter = 0; - clock_gettime(CLOCKID, &calculatedCycleStartTime[0]); - calculatedCycleStartTime[0].tv_nsec += pubIntervalNs; - nanoSecondFieldConversion(&calculatedCycleStartTime[0]); - - /* Set the callback -- used as a sentinel to detect an operational publisher */ - pubServer = server; - pubCallback = callback; - pubData = data; - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Switching the publisher cycle to %lf milliseconds", interval_ms); - - struct itimerspec timerspec; - int resultTimerCreate = 0; - pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS); - timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / SECONDS_AS_NANO_SECONDS); - timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS); - timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS)); - timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS); - resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL); - if(resultTimerCreate != 0) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Failed to arm the system timer"); - timer_delete(pubEventTimer); - return UA_STATUSCODE_BADINTERNALERROR; - } - - clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]); - calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs; - nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]); - - return UA_STATUSCODE_GOOD; -} - -static void -removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { - if(!pubCallback) - return; - timer_delete(pubEventTimer); - pubCallback = NULL; /* So that a new callback can be registered */ -} - -static void -addPubSubConfiguration(UA_Server* server) { - UA_NodeId connectionIdent; - UA_NodeId publishedDataSetIdent; - UA_NodeId writerGroupIdent; - - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = - UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - connectionConfig.enabled = true; - UA_NetworkAddressUrlDataType networkAddressUrl = - {UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConfig.publisherId.uint32 = UA_UInt32_random(); - - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, - &publishedDataSetIdent); - - UA_NodeId dataSetFieldIdentCounter; - UA_DataSetFieldConfig counterValue; - memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig)); - counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1"); - counterValue.field.variable.promotedField = UA_FALSE; - counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher; - counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - -#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES) - staticValueSource = UA_DataValue_new(); - UA_Variant_setScalar(&staticValueSource->value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]); - counterValue.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - counterValue.field.variable.rtValueSource.staticValueSource = &staticValueSource; -#endif - UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue, - &dataSetFieldIdentCounter); - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = PUB_INTERVAL; - writerGroupConfig.enabled = UA_FALSE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback; - UA_Server_addWriterGroup(server, connectionIdent, - &writerGroupConfig, &writerGroupIdent); - - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); - - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); -} - -static void -addServerNodes(UA_Server* server) { - UA_UInt64 value = 0; - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - UA_Variant_setScalar(&publisherAttr.value, &value, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter"); - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_DataSource dataSource; - dataSource.read = readPublishValue; - dataSource.write = NULL; - UA_Server_addDataSourceVariableNode(server, counterNodePublisher, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Publisher Counter"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - publisherAttr, dataSource, NULL, NULL); -} - -/* Stop signal */ -static void stopHandler(int sign) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); - running = UA_FALSE; -} - -int main(void) { - signal(SIGINT, stopHandler); - signal(SIGTERM, stopHandler); - - UA_Server *server = UA_Server_new(); - UA_ServerConfig *config = UA_Server_getConfig(server); - UA_ServerConfig_setDefault(config); - - addServerNodes(server); - addPubSubConfiguration(server); - - /* Run the server */ - UA_StatusCode retval = UA_Server_run(server, &running); -#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES) - if(staticValueSource != NULL) { - UA_DataValue_init(staticValueSource); - UA_DataValue_delete(staticValueSource); - } -#endif - UA_Server_delete(server); - - return (int)retval; -} diff --git a/examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c b/examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c deleted file mode 100644 index 502a1eb2b..000000000 --- a/examples/pubsub_realtime/attic/server_pubsub_rt_field_information_model.c +++ /dev/null @@ -1,194 +0,0 @@ -/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. - * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ - -#include -#include -#include -#include -#include - -#include -#include - -UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier; -UA_UInt32 *integerRTValue, *integerRTValue2; -UA_NodeId rtNodeId1, rtNodeId2; - -/* Info: It is still possible to create a RT-PubSub configuration without an - * information model node. Just set the DSF flags to 'rtInformationModelNode' -> - * true and 'rtInformationModelNode' -> false and provide the PTR to your self - * managed value source. */ - -static UA_NodeId -addVariable(UA_Server *server, char *name) { - /* Define the attribute of the myInteger variable node */ - UA_VariableAttributes attr = UA_VariableAttributes_default; - UA_UInt32 myInteger = 42; - UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]); - attr.description = UA_LOCALIZEDTEXT("en-US", name); - attr.displayName = UA_LOCALIZEDTEXT("en-US", name); - attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - - /* Add the variable node to the information model */ - UA_NodeId outNodeId; - UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, name); - UA_NodeId parentNodeId = UA_NS0ID(OBJECTSFOLDER); - UA_NodeId parentReferenceNodeId = UA_NS0ID(ORGANIZES); - UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId, - myIntegerName, UA_NS0ID(BASEDATAVARIABLETYPE), - attr, NULL, &outNodeId); - return outNodeId; -} - -/* If the external data source is written over the information model, the - * externalDataWriteCallback will be triggered. The user has to take care and assure - * that the write leads not to synchronization issues and race conditions. */ -static UA_StatusCode -externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId, - void *sessionContext, const UA_NodeId *nodeId, - void *nodeContext, const UA_NumericRange *range, - const UA_DataValue *data) { - /* It's possible to create a new DataValue here and use an atomic ptr switch - * to update the value without the need for locks e.g. UA_atomic_cmpxchg(); */ - if(UA_NodeId_equal(nodeId, &rtNodeId1)){ - memcpy(integerRTValue, data->value.data, sizeof(UA_UInt32)); - } else if(UA_NodeId_equal(nodeId, &rtNodeId2)){ - memcpy(integerRTValue2, data->value.data, sizeof(UA_UInt32)); - } - return UA_STATUSCODE_GOOD; -} - -static void -cyclicValueUpdateCallback_UpdateToMemory(UA_Server *server, void *data) { - *integerRTValue = (*integerRTValue)+1; - *integerRTValue2 = (*integerRTValue2)+1; -} - -static void -cyclicValueUpdateCallback_UpdateToStack(UA_Server *server, void *data) { - UA_Variant valueToWrite; - UA_UInt32 newValue = (*integerRTValue)+10; - UA_Variant_setScalar(&valueToWrite, &newValue, &UA_TYPES[UA_TYPES_UINT32]); - UA_Server_writeValue(server, rtNodeId1, valueToWrite); - - UA_Variant valueToWrite2; - UA_UInt32 newValue2 = (*integerRTValue2)+10; - UA_Variant_setScalar(&valueToWrite2, &newValue2, &UA_TYPES[UA_TYPES_UINT32]); - UA_Server_writeValue(server, rtNodeId2, valueToWrite2); -} - -int main(void){ - UA_Server *server = UA_Server_new(); - - /* Add one PubSubConnection */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = - UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - UA_NetworkAddressUrlDataType networkAddressUrl = - {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = 2234; - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier); - - /* Add one PublishedDataSet */ - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - - /* Add one DataSetField to the PDS */ - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent); - - /* Add RT configuration */ - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = 1000; - writerGroupConfig.writerGroupId = 100; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - UA_UadpWriterGroupMessageDataType writerGroupMessage; - UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage); - writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) - (UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER | - UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER); - UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage, - &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]); - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent); - - /* Add one DataSetWriter */ - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = 62541; - dataSetWriterConfig.keyFrameCount = 10; - /* Encode fields as RAW-Encoded */ - dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); - - /* Add new nodes */ - rtNodeId1 = addVariable(server, "RT value source 1"); - rtNodeId2 = addVariable(server, "RT value source 2"); - - /* Set the value backend to 'external value source' */ - integerRTValue = UA_UInt32_new(); - UA_DataValue *dataValueRT = UA_DataValue_new(); - dataValueRT->hasValue = UA_TRUE; - UA_Variant_setScalar(&dataValueRT->value, integerRTValue, &UA_TYPES[UA_TYPES_UINT32]); - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &dataValueRT; - valueBackend.backend.external.callback.userWrite = externalDataWriteCallback; - UA_Server_setVariableNode_valueBackend(server, rtNodeId1, valueBackend); - - /* Setup RT DataSetField config */ - UA_NodeId dsfNodeId; - UA_DataSetFieldConfig dsfConfig; - memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig.field.variable.publishParameters.publishedVariable = rtNodeId1; - dsfConfig.field.variable.fieldNameAlias = UA_STRING("Field 1"); - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dsfNodeId); - - /* Set the value backend of the above create node to 'external value source' */ - integerRTValue2 = UA_UInt32_new(); - *integerRTValue2 = 1000; - UA_DataValue *dataValue2RT = UA_DataValue_new(); - dataValue2RT->hasValue = true; - UA_Variant_setScalar(&dataValue2RT->value, integerRTValue2, &UA_TYPES[UA_TYPES_UINT32]); - UA_ValueBackend valueBackend2; - valueBackend2.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend2.backend.external.value = &dataValue2RT; - valueBackend2.backend.external.callback.userWrite = externalDataWriteCallback; - UA_Server_setVariableNode_valueBackend(server, rtNodeId2, valueBackend2); - - /* Setup second DataSetField config */ - UA_DataSetFieldConfig dsfConfig2; - memset(&dsfConfig2, 0, sizeof(UA_DataSetFieldConfig)); - dsfConfig2.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE; - dsfConfig2.field.variable.publishParameters.publishedVariable = rtNodeId2; - dsfConfig2.field.variable.fieldNameAlias = UA_STRING("Field 2"); - UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig2, NULL); - - UA_Server_enableAllPubSubComponents(server); - - UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToMemory, - NULL, 1000, NULL); - - UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToStack, - NULL, 5000, NULL); - - UA_StatusCode retval = UA_Server_runUntilInterrupt(server); - - UA_Server_delete(server); - UA_DataValue_delete(dataValueRT); - UA_DataValue_delete(dataValue2RT); - return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/examples/pubsub_realtime/attic/start_rt_publish.sh b/examples/pubsub_realtime/attic/start_rt_publish.sh deleted file mode 100755 index 4589f25d6..000000000 --- a/examples/pubsub_realtime/attic/start_rt_publish.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -#program path or install dir -IF=enp0s8 -CPU_NR=3 -PRIO=90 -OLDDIR=$(pwd) - -reset() { - #standard on most systems. ondemand -> Dynamic CPU-Freq. - echo ondemand >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor - cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle - for i in * - do - #~disable sleep state - echo 0 >$i/disable - done - - phy=$IF #get interface name - #get pid's from interface irq - for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1` - do - #retrive or set a process's CPU affinity - taskset -pc 0-$CPU_NR $i >/dev/null - #manipulate the real-time attributes of a process -p priority -f scheduling policy to SCHED_FIFO - chrt -pf 50 $i - done - #distribute hardware interrupts across processsors on a muliprocessor system - systemctl start irqbalance -} - -trap reset ERR -systemctl stop irqbalance - -phy=$IF -for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1` -do - taskset -pc $CPU_NR $i >/dev/null - chrt -pf $PRIO $i -done - -cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle -for i in `ls -1r` -do - echo 1 >$i/disable -done - -echo performance >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor - -cd $OLDDIR -taskset -c $CPU_NR chrt -f $PRIO $1 diff --git a/examples/pubsub_realtime/attic/vxworks/README.md b/examples/pubsub_realtime/attic/vxworks/README.md deleted file mode 100644 index 2453aa6fe..000000000 --- a/examples/pubsub_realtime/attic/vxworks/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Open62541 pubsub VxWorks TSN publisher - -This TSN publisher is a complement to [pubsub_interrupt_publish.c](../pubsub_interrupt_publish.c)(For more information, please refer to its [README](../README.md)). It is a VxWorks specific implementation with VxWorks-native TSN features like TSN Clock, TSN Stream, etc, which can be applied to hard realtime scenarios. - -In this publisher, TSN cycle is triggered by TSN Clock -- A high-precision hardware interrupt generated by a 1588 device. The ISR will wake up a task to publish OPC-UA packets, one packet per cycle. - -Considering in a typical VxWorks user scenario -- an embedded device with limited compute resources and probably without local storage, the performance will not be measured directly by this publisher. Instead, the publisher will collect all the necessary data, pack the data into an OPC-UA packet and publish it. - -The measurement data includes: - -1. The trigger time(1588 time) of each cycle. -2. Each wake-up time(1588 time) of the publisher task. -3. Each end time(1588 time) of the publisher task. - -## User interfaces - -This publisher provides 2 APIs to users: - -1. `open62541PubTSNStart` - - ```C - /** - * Create a publisher with/without TSN. - * eName: Ethernet interface name like "gei", "gem", etc - * eNameSize: The length of eName including "\0" - * unit: Unit NO of the ethernet interface - * stkIdx: Network stack index - * withTsn: true: Enable TSN; false: Disable TSN - * - * @return OK on success, otherwise ERROR - */ - STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn); - ``` - - In order to get the best performance, this API supports to associate a dedicated CPU core with TSN related interrupt and tasks. The stkIdx argument is used for this purpose. Its value is the CPU core number: 0 means Core 0, 1 means Core 1, etc. - -2. `open62541PubTSNStop`. This API is used to stop the publisher. - - ```C - void open62541PubTSNStop(); - ``` - -## Building the VxWorks TSN Publisher - -This publisher should be built under VxWorks' building environment. For specific building instructions, please refer to VxWorks' user manual. - -## Running the VxWorks TSN Publisher - -1. Before starting the publisher, you should make sure that PTP is well synchronized. For the usage of PTP, please refer to VxWorks' user manual. -2. Do TSN configuration by tsnConfig as below: - - ```sh - -> tsnConfig("gei", 4, 1, 0, "/romfs/62541.json") - ``` - - `62541.json` is a TSN configuration file. Below is one example configuration using 250 usecs cycle time. Adjust cycle_time and tx_time according to your system performance. - - ```json - { - "schedule": { - "cycle_time": 250000, - "start_sec": 0, - "start_nsec": 0 - }, - "stream_objects": [ - { - "stream": { - "name": "flow1", - "dst_mac": "01:00:5E:00:00:01", - "vid": 3000, - "pcp": 7, - "tclass": 7, - "tx_time": { - "offset": 200000 - } - } - } - ] - } - ``` - -3. Then run `open62541PubTSNStart`. Below is an example. - - ```sh - -> open62541PubTSNStart("gei", 4, 1, 3) - value = 0 = 0x0 - -> [1970-01-01 00:47:33.100 (UTC+0000)] warn/server Username/Password configured, but no encrypting SecurityPolicy. This can leak credentials on the ne. - [1970-01-01 00:47:33.100 (UTC+0000)] warn/userland AcceptAll Certificate Verification. Any remote certificate will be accepted. - [1970-01-01 00:47:33.100 (UTC+0000)] info/userland PubSub channel requested - [1970-01-01 00:47:33.100 (UTC+0000)] info/server Open PubSub ethernet connection. - [1970-01-01 00:47:33.100 (UTC+0000)] info/userland Adding a publisher with a cycle time of 0.250000 milliseconds - [1970-01-01 00:47:33.200 (UTC+0000)] info/network TCP network layer listening on opc.tcp://vxWorks:4840/ - ``` - -4. Run `open62541PubTSNStop` to stop the publisher. - - ```sh - -> open62541PubTSNStop - [1970-01-01 00:48:16.500 (UTC+0000)] info/server Stop TSN publisher - [1970-01-01 00:48:16.516 (UTC+0000)] info/network Shutting down the TCP network layer - [1970-01-01 00:48:16.516 (UTC+0000)] info/server PubSub cleanup was called. - value = 0 = 0x0 - ``` diff --git a/examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c b/examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c deleted file mode 100644 index 585815db9..000000000 --- a/examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c +++ /dev/null @@ -1,630 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2020, 2022, 2024 Wind River Systems, Inc. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#define ETH_PUBLISH_ADDRESS "opc.eth://01-00-5E-00-00-01" -#define MILLI_AS_NANO_SECONDS (1000 * 1000) -#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000) - -#define DATA_SET_WRITER_ID 62541 -#define TSN_TIMER_NO 0 -#define NS_PER_SEC 1000000000 /* nanoseconds per one second */ -#define NS_PER_MS 1000000 /* nanoseconds per 1 millisecond */ -#define TSN_TASK_PRIO 30 -#define TSN_TASK_STACKSZ 8192 - -#define KEY_STREAM_NAME "streamName" -#define KEY_STACK_IDX "stackIdx" - -static UA_NodeId seqNumNodeId; -static UA_NodeId cycleTriggerTimeNodeId; -static UA_NodeId taskBeginTimeNodeId; -static UA_NodeId taskEndTimeNodeId; -static UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */ -static UA_Server *pubServer; -static UA_Boolean running = true; -static void *pubData; -static TASK_ID tsnTask = TASK_ID_NULL; -static TASK_ID serverTask = TASK_ID_NULL; -static TSN_STREAM_CFG *streamCfg = NULL; - -/* The value to published */ -static UA_UInt32 sequenceNumber = 0; -static UA_UInt64 cycleTriggerTime = 0; -static UA_UInt64 lastCycleTriggerTime = 0; -static UA_UInt64 lastTaskBeginTime = 0; -static UA_UInt64 lastTaskEndTime = 0; -static UA_DataValue *staticValueSeqNum = NULL; -static UA_DataValue *staticValueCycTrig = NULL; -static UA_DataValue *staticValueCycBegin = NULL; -static UA_DataValue *staticValueCycEnd = NULL; -static clockid_t tsnClockId = 0; /* TSN clock ID */ -static char *ethName = NULL; -static int ethUnit = 0; -static char ethInterface[END_NAME_MAX]; -static SEM_ID msgSendSem = SEM_ID_NULL; -static UA_String streamName = {0, NULL}; -static uint32_t stackIndex = 0; -static UA_Double pubInterval = 0; -static bool withTSN = false; - -static UA_UInt64 ieee1588TimeGet() { - struct timespec ts; - (void)tsnClockTimeGet(tsnClockId, &ts); - return ((UA_UInt64)ts.tv_sec * NS_PER_SEC + (UA_UInt64)ts.tv_nsec); -} - -/* Signal handler */ -static void -publishInterrupt(_Vx_usr_arg_t arg) { - cycleTriggerTime = ieee1588TimeGet(); - if(running) { - (void)semGive(msgSendSem); - } -} - -/** - * **initTsnTimer** - * - * This function initializes a TSN timer. It connects a user defined routine - * to the interrupt handler and sets timer expiration rate. - * - * period is the period of the timer in nanoseconds. - * RETURNS: Clock Id or 0 if anything fails*/ -static clockid_t -initTsnTimer(uint32_t period) { - clockid_t cid; - uint32_t tickRate = 0; - - cid = tsnClockIdGet(ethName, ethUnit, TSN_TIMER_NO); - if(cid != 0) { - tickRate = NS_PER_SEC / period; - if(tsnTimerAllocate(cid) == ERROR) { - return 0; - } - if((tsnClockConnect(cid, (FUNCPTR)publishInterrupt, NULL) == ERROR) || - (tsnClockRateSet(cid, tickRate) == ERROR)) { - (void)tsnTimerRelease(cid); - return 0; - } - } - - /* Reroute TSN timer interrupt to a specific CPU core */ - if(tsnClockIntReroute(ethName, ethUnit, stackIndex) != OK) { - cid = 0; - } - return cid; -} - -/* The following three methods are originally defined in - * /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to - * use system interrupts instead of time-triggered callbacks in the OPC UA - * server control flow. */ - -static UA_StatusCode -addApplicationCallback(UA_Server *server, UA_NodeId identifier, - UA_ServerCallback callback, - void *data, - UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy, - UA_UInt64 *callbackId) { - if(pubCallback) { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "At most one publisher can be registered for interrupt callbacks"); - return UA_STATUSCODE_BADINTERNALERROR; - } - - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Adding a publisher with a cycle time of %lf milliseconds", interval_ms); - - /* Convert a double float value milliseconds into an integer value in nanoseconds */ - uint32_t interval = (uint32_t)(interval_ms * NS_PER_MS); - tsnClockId = initTsnTimer(interval); - if(tsnClockId == 0) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a TSN timer"); - return UA_STATUSCODE_BADINTERNALERROR; - } - /* Set the callback -- used as a sentinel to detect an operational publisher */ - pubServer = server; - pubCallback = callback; - pubData = data; - - if(tsnClockEnable (tsnClockId, NULL) == ERROR) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot enable a TSN timer"); - (void)tsnTimerRelease(tsnClockId); - tsnClockId = 0; - return UA_STATUSCODE_BADINTERNALERROR; - } - - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -changeApplicationCallbackInterval(UA_Server *server, UA_NodeId identifier, - UA_UInt64 callbackId, - UA_Double interval_ms, - UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Switching the publisher cycle to %lf milliseconds", interval_ms); - - /* We are not going to change the timer interval for this case */ - - return UA_STATUSCODE_GOOD; -} - -static void -removeApplicationPubSubCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) { - if(!pubCallback) { - return; - } - - /* Before release timer resource, wait for TSN task stopping first. */ - (void) semGive(msgSendSem); - if(tsnTask != TASK_ID_NULL) { - (void) taskWait(tsnTask, WAIT_FOREVER); - tsnTask = TASK_ID_NULL; - } - - /* It is safe to disable and release the timer first, then clear callback */ - if(tsnClockId != 0) { - (void)tsnClockDisable(tsnClockId); - (void)tsnTimerRelease(tsnClockId); - tsnClockId = 0; - } - - pubCallback = NULL; - pubServer = NULL; - pubData = NULL; -} - -static void -addPubSubConfiguration(UA_Server* server) { - UA_NodeId connectionIdent; - UA_NodeId publishedDataSetIdent; - UA_NodeId writerGroupIdent; - - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(connectionConfig)); - connectionConfig.name = UA_STRING("UDP-UADP Connection 1"); - connectionConfig.transportProfileUri = - UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - connectionConfig.enabled = true; - UA_NetworkAddressUrlDataType networkAddressUrl = - {UA_STRING(ethInterface), UA_STRING(ETH_PUBLISH_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.publisherId.numeric = UA_UInt32_random(); - - UA_KeyValuePair connectionOptions[2]; - - connectionOptions[0].key = UA_QUALIFIEDNAME(0, KEY_STREAM_NAME); - UA_Variant_setScalar(&connectionOptions[0].value, &streamName, &UA_TYPES[UA_TYPES_STRING]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, KEY_STACK_IDX); - UA_Variant_setScalar(&connectionOptions[1].value, &stackIndex, &UA_TYPES[UA_TYPES_UINT32]); - - connectionConfig.connectionPropertiesSize = 2; - connectionConfig.connectionProperties = connectionOptions; - - UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - publishedDataSetConfig.name = UA_STRING("Demo PDS"); - UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, - &publishedDataSetIdent); - - UA_DataSetFieldConfig dataSetFieldCfg; - UA_NodeId f4; - memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Sequence Number"); - dataSetFieldCfg.field.variable.promotedField = UA_FALSE; - dataSetFieldCfg.field.variable.publishParameters.publishedVariable = seqNumNodeId; - dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - - dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - staticValueSeqNum = UA_DataValue_new(); - UA_Variant_setScalar(&staticValueSeqNum->value, &sequenceNumber, &UA_TYPES[UA_TYPES_UINT32]); - staticValueSeqNum->value.storageType = UA_VARIANT_DATA_NODELETE; - dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueSeqNum; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f4); - - UA_NodeId f3; - memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Cycle Trigger Time"); - dataSetFieldCfg.field.variable.promotedField = UA_FALSE; - dataSetFieldCfg.field.variable.publishParameters.publishedVariable = cycleTriggerTimeNodeId; - dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - - dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - staticValueCycTrig = UA_DataValue_new(); - UA_Variant_setScalar(&staticValueCycTrig->value, &lastCycleTriggerTime, &UA_TYPES[UA_TYPES_UINT64]); - staticValueCycTrig->value.storageType = UA_VARIANT_DATA_NODELETE; - dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycTrig; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f3); - - UA_NodeId f2; - memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task Begin Time"); - dataSetFieldCfg.field.variable.promotedField = UA_FALSE; - dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskBeginTimeNodeId; - dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - - dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - staticValueCycBegin = UA_DataValue_new(); - UA_Variant_setScalar(&staticValueCycBegin->value, &lastTaskBeginTime, &UA_TYPES[UA_TYPES_UINT64]); - staticValueCycBegin->value.storageType = UA_VARIANT_DATA_NODELETE; - dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycBegin; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f2); - - - UA_NodeId f1; - memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig)); - dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task End Time"); - dataSetFieldCfg.field.variable.promotedField = UA_FALSE; - dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskEndTimeNodeId; - dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE; - - dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE; - staticValueCycEnd = UA_DataValue_new(); - UA_Variant_setScalar(&staticValueCycEnd->value, &lastTaskEndTime, &UA_TYPES[UA_TYPES_UINT64]); - staticValueCycEnd->value.storageType = UA_VARIANT_DATA_NODELETE; - dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycEnd; - - UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f1); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); - writerGroupConfig.name = UA_STRING("Demo WriterGroup"); - writerGroupConfig.publishingInterval = pubInterval; - writerGroupConfig.enabled = UA_FALSE; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE; - writerGroupConfig.pubsubManagerCallback.addCustomCallback = addApplicationCallback; - writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changeApplicationCallbackInterval; - writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removeApplicationPubSubCallback; - UA_Server_addWriterGroup(server, connectionIdent, - &writerGroupConfig, &writerGroupIdent); - - UA_NodeId dataSetWriterIdent; - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter"); - dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID; - dataSetWriterConfig.keyFrameCount = 10; - UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, - &dataSetWriterConfig, &dataSetWriterIdent); - - UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent); - UA_Server_enableWriterGroup(server, writerGroupIdent); -} - -static void -addServerNodes(UA_Server* server) { - UA_UInt64 initVal64 = 0; - UA_UInt32 initVal32 = 0; - - UA_NodeId folderId; - UA_NodeId_init(&folderId); - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher TSN"); - UA_Server_addObjectNode(server, UA_NODEID_NULL, - UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), - UA_QUALIFIEDNAME(1, "Publisher TSN"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), - oAttr, NULL, &folderId); - - UA_NodeId_init(&seqNumNodeId); - seqNumNodeId = UA_NODEID_STRING(1, "sequence.number"); - UA_VariableAttributes publisherAttr = UA_VariableAttributes_default; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId; - UA_Variant_setScalar(&publisherAttr.value, &initVal32, &UA_TYPES[UA_TYPES_UINT32]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Sequence Number"); - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Server_addVariableNode(server, seqNumNodeId, - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Sequence Number"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - publisherAttr, NULL, NULL); - UA_ValueBackend valueBackend; - valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL; - valueBackend.backend.external.value = &staticValueSeqNum; - UA_Server_setVariableNode_valueBackend(server, seqNumNodeId, valueBackend); - - UA_NodeId_init(&cycleTriggerTimeNodeId); - cycleTriggerTimeNodeId = UA_NODEID_STRING(1, "cycle.trigger.time"); - publisherAttr = UA_VariableAttributes_default; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cycle Trigger Time"); - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Server_addVariableNode(server, cycleTriggerTimeNodeId, - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Cycle Trigger Time"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - publisherAttr, NULL, NULL); - valueBackend.backend.external.value = &staticValueCycTrig; - UA_Server_setVariableNode_valueBackend(server, cycleTriggerTimeNodeId, valueBackend); - - UA_NodeId_init(&taskBeginTimeNodeId); - taskBeginTimeNodeId = UA_NODEID_STRING(1, "task.begin.time"); - publisherAttr = UA_VariableAttributes_default; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task Begin Time"); - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Server_addVariableNode(server, taskBeginTimeNodeId, - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Task Begin Time"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - publisherAttr, NULL, NULL); - valueBackend.backend.external.value = &staticValueCycBegin; - UA_Server_setVariableNode_valueBackend(server, taskBeginTimeNodeId, valueBackend); - - UA_NodeId_init(&taskEndTimeNodeId); - taskEndTimeNodeId = UA_NODEID_STRING(1, "task.end.time"); - publisherAttr = UA_VariableAttributes_default; - publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId; - UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]); - publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task End Time"); - publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; - UA_Server_addVariableNode(server, taskEndTimeNodeId, - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, "Task End Time"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - publisherAttr, NULL, NULL); - valueBackend.backend.external.value = &staticValueCycEnd; - UA_Server_setVariableNode_valueBackend(server, taskEndTimeNodeId, valueBackend); -} - -static void open62541EthTSNTask(void) { - uint64_t t = 0; - while(running) { - (void) semTake(msgSendSem, WAIT_FOREVER); - if(!running) { - break; - } - t = ieee1588TimeGet(); - - /* - * Because we cannot get the task end time of one packet before it is - * sent, we let one packet take its previous packet's cycleTriggerTime, - * taskBeginTime and taskEndTime. - */ - if(sequenceNumber == 0) { - lastCycleTriggerTime = 0; - lastTaskBeginTime = 0; - lastTaskEndTime = 0; - } - pubCallback(pubServer, pubData); - - sequenceNumber++; - lastCycleTriggerTime = cycleTriggerTime; - lastTaskBeginTime = t; - lastTaskEndTime = ieee1588TimeGet(); - } -} - -static void open62541ServerTask(void) { - UA_Server *server = UA_Server_new(); - if(server == NULL) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a server object"); - goto serverCleanup; - } - - UA_ServerConfig *config = UA_Server_getConfig(server); - UA_ServerConfig_setDefault(config); - - addServerNodes(server); - addPubSubConfiguration(server); - - /* Run the server */ - (void) UA_Server_run(server, &running); - -serverCleanup: - if(server != NULL) { - UA_Server_delete(server); - server = NULL; - } -} - -static bool initTSNStream(char *eName, size_t eNameSize, int unit) { - streamCfg = tsnConfigFind (eName, eNameSize, unit); - if(streamCfg == NULL) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find TSN configuration for %s%d", eName, unit); - return false; - } - if(streamCfg->streamCount == 0) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find stream defined for %s%d", eName, unit); - return false; - } - else { - if(withTSN) { - streamName = UA_STRING(streamCfg->streamObjs[0].stream.name); - } else { - UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "No TSN stream associated."); - streamName = UA_STRING_NULL; - } - - } - pubInterval = (UA_Double)((streamCfg->schedule.cycleTime * 1.0) / NS_PER_MS); - return true; -} - -static bool initTSNTask() { - tsnTask = taskSpawn ((char *)"tTsnPub", TSN_TASK_PRIO, 0, TSN_TASK_STACKSZ, (FUNCPTR)open62541EthTSNTask, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - if(tsnTask == TASK_ID_ERROR) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a TSN task"); - return false; - } - - /* Reroute TSN task to a specific CPU core */ -#ifdef _WRS_CONFIG_SMP - unsigned int ncpus = vxCpuConfiguredGet (); - cpuset_t cpus; - - if(stackIndex < ncpus) { - CPUSET_ZERO (cpus); - CPUSET_SET (cpus, stackIndex); - if(taskCpuAffinitySet (tsnTask, cpus) != OK) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex); - return false; - } - } -#endif - return true; -} - -static bool initServerTask() { - serverTask = taskSpawn((char *)"tPubServer", TSN_TASK_PRIO + 5, 0, TSN_TASK_STACKSZ*2, (FUNCPTR)open62541ServerTask, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - if(serverTask == TASK_ID_ERROR) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a server task"); - return false; - } - - /* Reroute TSN task to a specific CPU core */ -#ifdef _WRS_CONFIG_SMP - unsigned int ncpus = vxCpuConfiguredGet(); - cpuset_t cpus; - - if(stackIndex < ncpus) { - CPUSET_ZERO(cpus); - CPUSET_SET(cpus, stackIndex); - if(taskCpuAffinitySet(serverTask, cpus) != OK) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex); - return false; - } - } -#endif - return true; -} - -void open62541PubTSNStop() { - UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Stop TSN publisher"); - running = UA_FALSE; - if(serverTask != TASK_ID_NULL) { - (void) taskWait(serverTask, WAIT_FOREVER); - serverTask = TASK_ID_NULL; - } - (void) semGive(msgSendSem); - if(tsnTask != TASK_ID_NULL) { - (void) taskWait(tsnTask, WAIT_FOREVER); - tsnTask = TASK_ID_NULL; - } - if(msgSendSem != NULL) { - (void)semDelete(msgSendSem); - msgSendSem = SEM_ID_NULL; - } - - ethName = NULL; - ethUnit = 0; - streamName = UA_STRING_NULL; - stackIndex = 0; - withTSN = false; - pubInterval = 0; - streamCfg = NULL; - - if(staticValueSeqNum != NULL) { - UA_DataValue_delete(staticValueSeqNum); - staticValueSeqNum = NULL; - } - if(staticValueCycTrig != NULL) { - UA_DataValue_delete(staticValueCycTrig); - staticValueCycTrig = NULL; - } - if(staticValueCycBegin != NULL) { - UA_DataValue_delete(staticValueCycBegin); - staticValueCycBegin = NULL; - } - if(staticValueCycEnd != NULL) { - UA_DataValue_delete(staticValueCycEnd); - staticValueCycEnd = NULL; - } - sequenceNumber = 0; - cycleTriggerTime = 0; - lastCycleTriggerTime = 0; - lastTaskBeginTime = 0; - lastTaskEndTime = 0; -} - -/** - * Create a publisher with/without TSN. - * eName: Ethernet interface name like "gei", "gem", etc - * eNameSize: The length of eName including "\0" - * unit: Unit NO of the ethernet interface - * stkIdx: Network stack index - * withTsn: true: Enable TSN; false: Disable TSN - * - * @return OK on success, otherwise ERROR - */ -STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn) { - if((eName == NULL) || (eNameSize == 0)) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Ethernet interface name is invalid"); - return ERROR; - } - - ethName = eName; - ethUnit = unit; - stackIndex = stkIdx; - withTSN = withTsn; - snprintf(ethInterface, sizeof(ethInterface), "%s%d", eName, unit); - - if(!initTSNStream(eName, eNameSize, unit)) { - goto startCleanup; - } - - /* Create a binary semaphore which is used by the TSN timer to wake up the sender task */ - msgSendSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY); - if(msgSendSem == SEM_ID_NULL) { - UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot create a semaphore"); - goto startCleanup; - } - running = true; - if(!initTSNTask()) { - goto startCleanup; - } - - if(!initServerTask()) { - goto startCleanup; - } - - - return OK; -startCleanup: - open62541PubTSNStop(); - return ERROR; -} From 2eef339f5af369501d6cc5a2e1782bddce4c27cc Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 23:05:29 +0100 Subject: [PATCH 152/158] refactor(tests): Cosmetic cleanups in check_pubsub_publisherid.c --- tests/pubsub/check_pubsub_publisherid.c | 83 ++++++++++++------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/tests/pubsub/check_pubsub_publisherid.c b/tests/pubsub/check_pubsub_publisherid.c index 1c1049bd1..e175077b2 100644 --- a/tests/pubsub/check_pubsub_publisherid.c +++ b/tests/pubsub/check_pubsub_publisherid.c @@ -180,12 +180,11 @@ AddDataSetReader(UA_NodeId *pReaderGroupId, char *pName, pDataSetMetaData->fields = (UA_FieldMetaData*) UA_Array_new (pDataSetMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]); - UA_FieldMetaData_init (&pDataSetMetaData->fields[0]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_INT32].typeId, - &pDataSetMetaData->fields[0].dataType); - pDataSetMetaData->fields[0].builtInType = UA_NS0ID_INT32; - pDataSetMetaData->fields[0].name = UA_STRING ("Int32 Var"); - pDataSetMetaData->fields[0].valueRank = -1; + UA_FieldMetaData_init(pDataSetMetaData->fields); + UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT32].typeId, &pDataSetMetaData->fields->dataType); + pDataSetMetaData->fields->builtInType = UA_NS0ID_INT32; + pDataSetMetaData->fields->name = UA_STRING ("Int32 Var"); + pDataSetMetaData->fields->valueRank = -1; ck_assert(UA_Server_addDataSetReader(server, *pReaderGroupId, &readerConfig, opDataSetReaderId) == UA_STATUSCODE_GOOD); UA_UadpDataSetReaderMessageDataType_delete(dsReaderMessage); @@ -290,8 +289,8 @@ static void DoTest_1_Connection(UA_PublisherId publisherId) { UA_NodeId_init(&PDSId_Conn1_WG1_PDS1); AddPublishedDataSet(&WGId_Conn1_WG1, "Conn1_WG1_PDS1", "Conn1_WG1_DS1", 1, - &PDSId_Conn1_WG1_PDS1, &publisherVarIds[0], - &fastPathPublisherDataValues[0], &DsWId_Conn1_WG1_DS1); + &PDSId_Conn1_WG1_PDS1, publisherVarIds, + fastPathPublisherDataValues, &DsWId_Conn1_WG1_DS1); UA_NodeId RGId_Conn1_RG1; UA_NodeId_init(&RGId_Conn1_RG1); @@ -299,21 +298,21 @@ static void DoTest_1_Connection(UA_PublisherId publisherId) { UA_NodeId DSRId_Conn1_RG1_DSR1; UA_NodeId_init(&DSRId_Conn1_RG1_DSR1); AddDataSetReader(&RGId_Conn1_RG1, "Conn1_RG1_DSR1", publisherId, 1, 1, - &subscriberVarIds[0], &fastPathSubscriberDataValues[0], + subscriberVarIds, fastPathSubscriberDataValues, &DSRId_Conn1_RG1_DSR1); /* set groups operational */ ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); /* check that publish/subscribe works -> set some test values */ - ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, &publisherVarIds[0], &subscriberVarIds[0], - &fastPathPublisherDataValues[0], &fastPathSubscriberDataValues[0], 10, 100); + ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, publisherVarIds, subscriberVarIds, + fastPathPublisherDataValues, fastPathSubscriberDataValues, 10, 100); - ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, &publisherVarIds[0], &subscriberVarIds[0], - &fastPathPublisherDataValues[0], &fastPathSubscriberDataValues[0], 33, 100); + ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, publisherVarIds, subscriberVarIds, + fastPathPublisherDataValues, fastPathSubscriberDataValues, 33, 100); - ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, &publisherVarIds[0], &subscriberVarIds[0], - &fastPathPublisherDataValues[0], &fastPathSubscriberDataValues[0], 44, 100); + ValidatePublishSubscribe(DOTEST_1_CONNECTION_MAX_VARS, publisherVarIds, subscriberVarIds, + fastPathPublisherDataValues, fastPathSubscriberDataValues, 44, 100); /* set groups to disabled */ UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "disable groups"); @@ -460,8 +459,8 @@ static void DoTest_multiple_Connections(void) { UA_NodeId PDSId_Conn1_WG1_PDS1; UA_NodeId_init(&PDSId_Conn1_WG1_PDS1); AddPublishedDataSet(&WGId_Conn1_WG1, "Conn1_WG1_PDS1", "Conn1_WG1_DS1", - DSW_Id, &PDSId_Conn1_WG1_PDS1, &publisherVarIds[0], - &fastPathPublisherDataValues[0], &DsWId_Conn1_WG1_DS1); + DSW_Id, &PDSId_Conn1_WG1_PDS1, publisherVarIds, + fastPathPublisherDataValues, &DsWId_Conn1_WG1_DS1); PublishedDataSetIds[0] = PDSId_Conn1_WG1_PDS1; /* setup Connection 2: */ @@ -600,8 +599,8 @@ static void DoTest_multiple_Connections(void) { UA_NodeId DSRId_Conn2_RG1_DSR1; UA_NodeId_init(&DSRId_Conn2_RG1_DSR1); AddDataSetReader(&RGId_Conn2_RG1, "Conn2_RG1_DSR1", - Conn1_PublisherId, WG_Id, DSW_Id, &subscriberVarIds[0], - &fastPathSubscriberDataValues[0], &DSRId_Conn2_RG1_DSR1); + Conn1_PublisherId, WG_Id, DSW_Id, subscriberVarIds, + fastPathSubscriberDataValues, &DSRId_Conn2_RG1_DSR1); ReaderGroupIds[1] = RGId_Conn2_RG1; /* setup Connection 3: */ @@ -815,7 +814,7 @@ static void DoTest_string_PublisherId(void) { UA_NodeId PDSId_Conn1_WG1_PDS1; UA_NodeId_init(&PDSId_Conn1_WG1_PDS1); AddPublishedDataSet(&WGId_Conn1_WG1, "Conn1_WG1_PDS1", "Conn1_WG1_DS1", DSW_Id, &PDSId_Conn1_WG1_PDS1, - &publisherVarIds[0], &fastPathPublisherDataValues[0], &DsWId_Conn1_WG1_DS1); + publisherVarIds, fastPathPublisherDataValues, &DsWId_Conn1_WG1_DS1); PublishedDataSetIds[0] = PDSId_Conn1_WG1_PDS1; /* setup Connection 2: */ @@ -927,7 +926,7 @@ static void DoTest_string_PublisherId(void) { UA_NodeId_init(&DSRId_Conn2_RG1_DSR1); AddDataSetReader(&RGId_Conn2_RG1, "Conn2_RG1_DSR1", Conn1_PublisherId, WG_Id, DSW_Id, - &subscriberVarIds[0], &fastPathSubscriberDataValues[0], &DSRId_Conn2_RG1_DSR1); + subscriberVarIds, fastPathSubscriberDataValues, &DSRId_Conn2_RG1_DSR1); ReaderGroupIds[1] = RGId_Conn2_RG1; /* setup Connection 3: */ @@ -1117,8 +1116,8 @@ START_TEST(Test_string_publisherId_file_config) { UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, "Published Int32"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - attr, NULL, &(publisherVarIds[0]))); - UA_NodeId_copy(&(publisherVarIds[0]), &(pdsDataItems->publishedData->publishedVariable)); + attr, NULL, publisherVarIds)); + UA_NodeId_copy(publisherVarIds, &pdsDataItems->publishedData->publishedVariable); pds->dataSetSource.encoding = UA_EXTENSIONOBJECT_DECODED; pds->dataSetSource.content.decoded.type = &UA_TYPES[UA_TYPES_PUBLISHEDDATAITEMSDATATYPE]; pds->dataSetSource.content.decoded.data = pdsDataItems; @@ -1164,7 +1163,7 @@ START_TEST(Test_string_publisherId_file_config) { wg->dataSetWritersSize = 1; wg->dataSetWriters = UA_DataSetWriterDataType_new(); ck_assert(wg->dataSetWriters != 0); - UA_DataSetWriterDataType *dsw = &wg->dataSetWriters[0]; + UA_DataSetWriterDataType *dsw = wg->dataSetWriters; dsw->name = UA_STRING_ALLOC("DataSetWriter 1"); dsw->dataSetWriterId = 1; dsw->dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_NONE; @@ -1182,7 +1181,7 @@ START_TEST(Test_string_publisherId_file_config) { connection->readerGroupsSize = 1; connection->readerGroups = UA_ReaderGroupDataType_new(); ck_assert(connection->readerGroups != 0); - UA_ReaderGroupDataType *rg = &connection->readerGroups[0]; + UA_ReaderGroupDataType *rg = connection->readerGroups; UA_ReaderGroupDataType_init(rg); rg->name = UA_STRING_ALLOC("ReaderGroup 1"); rg->maxNetworkMessageSize = MAX_NETWORKMESSAGE_SIZE; @@ -1191,7 +1190,7 @@ START_TEST(Test_string_publisherId_file_config) { rg->dataSetReadersSize = 1; rg->dataSetReaders = UA_DataSetReaderDataType_new(); ck_assert(rg->dataSetReaders != 0); - UA_DataSetReaderDataType *dsr = &rg->dataSetReaders[0]; + UA_DataSetReaderDataType *dsr = rg->dataSetReaders; UA_DataSetReaderDataType_init(dsr); dsr->name = UA_STRING_ALLOC("DataSetReader 1"); UA_Variant_setScalarCopy(&dsr->publisherId, &publisherIdString, &UA_TYPES[UA_TYPES_STRING]); @@ -1227,8 +1226,8 @@ START_TEST(Test_string_publisherId_file_config) { ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_addVariableNode(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Subscribed Int32"), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, &(subscriberVarIds[0]))); - UA_NodeId_copy(&(subscriberVarIds[0]), &(targetVars->targetVariables->targetNodeId)); + UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, subscriberVarIds)); + UA_NodeId_copy(subscriberVarIds, &targetVars->targetVariables->targetNodeId); dsr->subscribedDataSet.encoding = UA_EXTENSIONOBJECT_DECODED; dsr->subscribedDataSet.content.decoded.type = &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]; dsr->subscribedDataSet.content.decoded.data = targetVars; @@ -1272,17 +1271,17 @@ START_TEST(Test_string_publisherId_file_config) { ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_enableAllPubSubComponents(server)); /* check that publish/subscribe works -> set some test values */ - ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, &publisherVarIds[0], - &subscriberVarIds[0], &fastPathPublisherDataValues[0], - &fastPathSubscriberDataValues[0], 10, 100); + ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, publisherVarIds, + subscriberVarIds, fastPathPublisherDataValues, + fastPathSubscriberDataValues, 10, 100); - ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, &publisherVarIds[0], - &subscriberVarIds[0], &fastPathPublisherDataValues[0], - &fastPathSubscriberDataValues[0], 33, 100); + ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, publisherVarIds, + subscriberVarIds, fastPathPublisherDataValues, + fastPathSubscriberDataValues, 33, 100); - ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, &publisherVarIds[0], - &subscriberVarIds[0], &fastPathPublisherDataValues[0], - &fastPathSubscriberDataValues[0], 44, 100); + ValidatePublishSubscribe(STRING_PUBLISHERID_FILE_MAX_COMPONENTS, publisherVarIds, + subscriberVarIds, fastPathPublisherDataValues, + fastPathSubscriberDataValues, 44, 100); UA_ByteString_clear(&encodedConfigDataBuffer); @@ -1360,8 +1359,8 @@ static void DoTest_multiple_Groups(void) { UA_NodeId PDSId_Conn1_WG1_PDS1; UA_NodeId_init(&PDSId_Conn1_WG1_PDS1); AddPublishedDataSet(&WGId_Conn1_WG1, "Conn1_WG1_PDS1", "Conn1_WG1_DS1", DSW_Id, - &PDSId_Conn1_WG1_PDS1, &publisherVarIds[0], - &fastPathPublisherDataValues[0], &DsWId_Conn1_WG1_DS1); + &PDSId_Conn1_WG1_PDS1, publisherVarIds, + fastPathPublisherDataValues, &DsWId_Conn1_WG1_DS1); PublishedDataSetIds[0] = PDSId_Conn1_WG1_PDS1; /* WriterGroup 2 */ @@ -1562,7 +1561,7 @@ static void DoTest_multiple_Groups(void) { UA_NodeId DSRId_Conn2_RG2_DSR1; UA_NodeId_init(&DSRId_Conn2_RG2_DSR1); AddDataSetReader(&RGId_Conn2_RG2, "Conn2_RG2_DSR1", Conn1_PublisherId, Conn1_WG1_Id, - DSW_Id, &subscriberVarIds[0], &fastPathSubscriberDataValues[0], + DSW_Id, subscriberVarIds, fastPathSubscriberDataValues, &DSRId_Conn2_RG2_DSR1); ReaderGroupIds[5] = RGId_Conn2_RG2; @@ -1713,7 +1712,7 @@ static void DoTest_multiple_DataSets(void) { UA_NodeId_init(&PDSId_Conn1_WG1_PDS1); const UA_UInt32 Conn1_WG1_DSW1_Id = 1; AddPublishedDataSet(&WGId_Conn1_WG1, "Conn1_WG1_PDS1", "Conn1_WG1_DS1", Conn1_WG1_DSW1_Id, &PDSId_Conn1_WG1_PDS1, - &publisherVarIds[0], &fastPathPublisherDataValues[0], &DsWId_Conn1_WG1_DS1); + publisherVarIds, fastPathPublisherDataValues, &DsWId_Conn1_WG1_DS1); PublishedDataSetIds[0] = PDSId_Conn1_WG1_PDS1; /* DataSetWriter 2 */ @@ -1818,7 +1817,7 @@ static void DoTest_multiple_DataSets(void) { UA_NodeId_init(&DSRId_Conn2_RG1_DSR1); AddDataSetReader(&RGId_Conn2_RG1, "Conn2_RG1_DSR1", Conn1_PublisherId, WG_Id, Conn1_WG1_DSW1_Id, - &subscriberVarIds[0], &fastPathSubscriberDataValues[0], &DSRId_Conn2_RG1_DSR1); + subscriberVarIds, fastPathSubscriberDataValues, &DSRId_Conn2_RG1_DSR1); /* DataSetReader 2 */ UA_NodeId DSRId_Conn2_RG1_DSR2; From 962d6e79ec5681458d9cb9134a730923556b0df9 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Fri, 17 Jan 2025 23:44:08 +0100 Subject: [PATCH 153/158] feat(doc): Add an entry for the new Realtime-PubSub model in the CHANGES.md --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1449e08b7..20dd400fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,14 @@ refactorings and bug fixes are not reported here. # Development +### New Realtime-PubSub model + +The new Realtime-PubSub model builds upon two new public APIS: (i) The +possibility to integrate custom state machines to control the state of +PubSub-components and (ii) the generation of offset-tables for the content of +PubSub NetworkMessages. The approach is described in +/examples/pubsub_realtime/README.md with code examples in the same folder. + ### JSON encoding changed with the v1.05 specification The JSON encoding was reworked for the v1.05 version of the OPC UA From 061d76bfbcb9f069a813bd2250852e290166e5b7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Jan 2025 17:27:39 +0100 Subject: [PATCH 154/158] refactor(tests): Remove pubsub "attic" of outdated tests --- .../check_pubsub_connection_ethernet_etf.c | 302 ---------------- .../attic/check_pubsub_connection_xdp.c | 330 ------------------ .../attic/check_pubsub_publish_ethernet_etf.c | 202 ----------- 3 files changed, 834 deletions(-) delete mode 100644 tests/pubsub/attic/check_pubsub_connection_ethernet_etf.c delete mode 100644 tests/pubsub/attic/check_pubsub_connection_xdp.c delete mode 100644 tests/pubsub/attic/check_pubsub_publish_ethernet_etf.c diff --git a/tests/pubsub/attic/check_pubsub_connection_ethernet_etf.c b/tests/pubsub/attic/check_pubsub_connection_ethernet_etf.c deleted file mode 100644 index f3148ddcf..000000000 --- a/tests/pubsub/attic/check_pubsub_connection_ethernet_etf.c +++ /dev/null @@ -1,302 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2019 Kalycito Infotech Private Limited - */ - -#include -#include -#include -#include -#include - -#include "ua_pubsub.h" -#include "ua_server_internal.h" -#include "ua_pubsub_networkmessage.h" -#include "test_helpers.h" - -/* Adjust your configuration globally for the ethernet tests here: */ -#include "ethernet_config.h" - -#define TRANSPORT_PROFILE_URI "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define SOCKET_PRIORITY 3 - -UA_Server *server = NULL; - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -START_TEST(AddConnectionsWithMinimalValidConfiguration){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("Ethernet ETF Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(&server->pubSubManager.connections.tqh_first->listEntry.tqe_next != NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 2); -} END_TEST - -START_TEST(AddRemoveAddConnectionWithMinimalValidConfiguration){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("Ethernet ETF Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_NodeId connectionIdent; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); - retVal |= UA_Server_removePubSubConnection(server, connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert(&server->pubSubManager.connections.tqh_first->listEntry.tqe_next != NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(AddConnectionWithInvalidAddress){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("Ethernet ETF Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING("opc.eth://a0:36:9f:04:5b:11")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); -} END_TEST - -START_TEST(AddConnectionWithInvalidInterface){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("Ethernet ETF Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); -} END_TEST - -START_TEST(AddConnectionWithUnknownTransportURL){ - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("Ethernet ETF Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/unknown-eth-uadp"); - UA_NodeId connectionIdent; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(AddConnectionWithNullConfig){ - UA_StatusCode retVal; - retVal = UA_Server_addPubSubConnection(server, NULL, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(AddSingleConnectionWithMaximalConfiguration){ - UA_NetworkAddressUrlDataType networkAddressUrlData = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant address; - UA_Variant_setScalar(&address, &networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - UA_KeyValuePair connectionOptions[5]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_UInt32 ttl = 10; - UA_Variant_setScalar(&connectionOptions[0].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "loopback"); - UA_Boolean loopback = UA_FALSE; - UA_Variant_setScalar(&connectionOptions[1].value, &loopback, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Boolean reuse = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[2].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[3].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[4].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[4].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - - UA_PubSubConnectionConfig connectionConf; - memset(&connectionConf, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConf.name = UA_STRING("Ethernet ETF Connection"); - connectionConf.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - connectionConf.enabled = true; - connectionConf.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; - connectionConf.publisherId.id.uint32 = 223344; - connectionConf.connectionProperties.map = connectionOptions; - connectionConf.connectionProperties.mapSize = 5; - connectionConf.address = address; - UA_NodeId connection; - UA_StatusCode retVal = UA_Server_addPubSubConnection(server, &connectionConf, &connection); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); -} END_TEST - -START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ - UA_NetworkAddressUrlDataType networkAddressUrlData = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant address; - UA_Variant_setScalar(&address, &networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - UA_KeyValuePair connectionOptions[5]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_UInt32 ttl = 10; - UA_Variant_setScalar(&connectionOptions[0].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "loopback"); - UA_Boolean loopback = UA_FALSE; - UA_Variant_setScalar(&connectionOptions[1].value, &loopback, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Boolean reuse = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[2].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[3].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[4].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[4].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - - UA_PubSubConnectionConfig connectionConf; - memset(&connectionConf, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConf.name = UA_STRING("Ethernet ETF Connection"); - connectionConf.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - connectionConf.enabled = true; - connectionConf.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; - connectionConf.publisherId.id.uint32 = 223344; - connectionConf.connectionProperties.map = connectionOptions; - connectionConf.connectionProperties.mapSize = 5; - connectionConf.address = address; - - UA_NodeId connection; - UA_StatusCode retVal = UA_Server_addPubSubConnection(server, &connectionConf, &connection); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - retVal |= UA_Server_getPubSubConnectionConfig(server, connection, &connectionConfig); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(connectionConfig.connectionProperties.mapSize == connectionConf.connectionProperties.mapSize); - ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); - ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); - UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); - ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) == - UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE])); - for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ - ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); - ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties.map[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties.map[i].value)); - } - UA_PubSubConnectionConfig_clear(&connectionConfig); -} END_TEST - -int main(void) { - if(SKIP_ETHERNET && strlen(SKIP_ETHERNET) > 0) - return EXIT_SUCCESS; - - TCase *tc_add_pubsub_connections_minimal_config = tcase_create("Create PubSub Ethernet ETF Connections with minimal valid config"); - tcase_add_checked_fixture(tc_add_pubsub_connections_minimal_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_minimal_config, AddConnectionsWithMinimalValidConfiguration); - tcase_add_test(tc_add_pubsub_connections_minimal_config, AddRemoveAddConnectionWithMinimalValidConfiguration); - - TCase *tc_add_pubsub_connections_invalid_config = tcase_create("Create PubSub Ethernet ETF Connections with invalid configurations"); - tcase_add_checked_fixture(tc_add_pubsub_connections_invalid_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithInvalidAddress); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithInvalidInterface); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithUnknownTransportURL); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithNullConfig); - - TCase *tc_add_pubsub_connections_maximal_config = tcase_create("Create PubSub Ethernet ETF Connections with maximal valid config"); - tcase_add_checked_fixture(tc_add_pubsub_connections_maximal_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_maximal_config, AddSingleConnectionWithMaximalConfiguration); - tcase_add_test(tc_add_pubsub_connections_maximal_config, GetMaximalConnectionConfigurationAndCompareValues); - - Suite *s = suite_create("PubSub Ethernet ETF connection creation"); - suite_add_tcase(s, tc_add_pubsub_connections_minimal_config); - suite_add_tcase(s, tc_add_pubsub_connections_invalid_config); - suite_add_tcase(s, tc_add_pubsub_connections_maximal_config); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} - - diff --git a/tests/pubsub/attic/check_pubsub_connection_xdp.c b/tests/pubsub/attic/check_pubsub_connection_xdp.c deleted file mode 100644 index e6599bc23..000000000 --- a/tests/pubsub/attic/check_pubsub_connection_xdp.c +++ /dev/null @@ -1,330 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2019-2020 Kalycito Infotech Private Limited - */ - -#include -#include -#include -#include - -#include "ua_server_internal.h" -#include "ua_pubsub.h" -#include "test_helpers.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#define MULTICAST_MAC_ADDRESS "opc.eth://01-00-5E-00-00-01" -#define RECEIVE_QUEUE_0 0 -#define RECEIVE_QUEUE_1 1 -#define RECEIVE_QUEUE_2 2 -#define RECEIVE_QUEUE_3 3 -#define XDP_FLAG XDP_FLAGS_SKB_MODE - -UA_Server *server = NULL; -UA_String ethernetInterface; - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -static void -usage(char *progname) { - printf("usage: %s \n", progname); - printf("Provide the Interface parameter to run the application. Exiting \n"); -} - -START_TEST(AddConnectionsWithMinimalValidConfiguration){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - /* Connection options are given as Key/Value Pairs. */ - UA_KeyValuePair connectionOptions[3]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); - UA_UInt32 flags = XDP_FLAG; - UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); - UA_UInt32 rxqueue = RECEIVE_QUEUE_2; - UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - connectionConfig.connectionProperties.mapSize = 3; - connectionConfig.connectionProperties.map = connectionOptions; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); - rxqueue = RECEIVE_QUEUE_1; - UA_Variant_setScalar(&connectionOptions[1].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(&server->pubSubManager.connections.tqh_first->listEntry.tqe_next != NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 2); -} END_TEST - - -START_TEST(AddRemoveAddConnectionWithMinimalValidConfiguration){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - UA_NodeId connectionIdent; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); - retVal |= UA_Server_removePubSubConnection(server, connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert(&server->pubSubManager.connections.tqh_first->listEntry.tqe_next != NULL); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(AddConnectionsWithInvalidConfiguration){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - /* Connection options are given as Key/Value Pairs.*/ - UA_KeyValuePair connectionOptions[3]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); - UA_UInt32 flags = XDP_FLAG; - UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); - UA_UInt32 rxqueue = RECEIVE_QUEUE_2; - UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 3; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); -} END_TEST - -START_TEST(AddConnectionWithInvalidAddress){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {ethernetInterface, UA_STRING("opc.eth://a0:36:9f:04:5b:11")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); -} END_TEST - -START_TEST(AddConnectionWithInvalidInterface){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING("eth0"), UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, NULL); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); -} END_TEST - -START_TEST(AddConnectionWithUnknownTransportURL){ - UA_StatusCode retVal; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("XDP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/unknown-eth-uadp"); - UA_NodeId connectionIdent; - retVal = UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(AddConnectionWithNullConfig){ - UA_StatusCode retVal; - retVal = UA_Server_addPubSubConnection(server, NULL, NULL); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 0); - ck_assert_int_ne(retVal, UA_STATUSCODE_GOOD); - } END_TEST - -START_TEST(AddSingleConnectionWithMaximalConfiguration){ - UA_NetworkAddressUrlDataType networkAddressUrlData = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant address; - UA_Variant_setScalar(&address, &networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - UA_KeyValuePair connectionOptions[4]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_UInt32 ttl = 10; - UA_Variant_setScalar(&connectionOptions[1].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "loopback"); - UA_Boolean loopback = UA_FALSE; - UA_Variant_setScalar(&connectionOptions[2].value, &loopback, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Boolean reuse = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[3].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - - UA_PubSubConnectionConfig connectionConf; - memset(&connectionConf, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConf.name = UA_STRING("XDP Connection"); - connectionConf.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - connectionConf.enabled = true; - connectionConf.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConf.publisherId.uint32 = 223344; - connectionConf.connectionProperties.mapSize = 4; - connectionConf.connectionProperties.map = connectionOptions; - connectionConf.address = address; - UA_NodeId connection; - UA_StatusCode retVal = UA_Server_addPubSubConnection(server, &connectionConf, &connection); - ck_assert_uint_eq(server->pubSubManager.connectionsSize, 1); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(! TAILQ_EMPTY(&server->pubSubManager.connections)); -} END_TEST - -START_TEST(GetMaximalConnectionConfigurationAndCompareValues){ - UA_NetworkAddressUrlDataType networkAddressUrlData = {ethernetInterface, UA_STRING(MULTICAST_MAC_ADDRESS)}; - UA_Variant address; - UA_Variant_setScalar(&address, &networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - UA_KeyValuePair connectionOptions[5]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); - UA_Boolean enableXdp = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); - UA_UInt32 rxqueue = RECEIVE_QUEUE_2; - UA_Variant_setScalar(&connectionOptions[1].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[2].key = UA_QUALIFIEDNAME(0, "ttl"); - UA_UInt32 ttl = 10; - UA_Variant_setScalar(&connectionOptions[2].value, &ttl, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[3].key = UA_QUALIFIEDNAME(0, "loopback"); - UA_Boolean loopback = UA_FALSE; - UA_Variant_setScalar(&connectionOptions[3].value, &loopback, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionOptions[4].key = UA_QUALIFIEDNAME(0, "reuse"); - UA_Boolean reuse = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[4].value, &reuse, &UA_TYPES[UA_TYPES_BOOLEAN]); - - UA_PubSubConnectionConfig connectionConf; - memset(&connectionConf, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConf.name = UA_STRING("XDP Connection"); - connectionConf.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); - connectionConf.enabled = true; - connectionConf.publisherIdType = UA_PUBLISHERIDTYPE_UINT32; - connectionConf.publisherId.uint32 = 223344; - connectionConf.connectionProperties.mapSize = 5; - connectionConf.connectionProperties.map = connectionOptions; - connectionConf.address = address; - UA_NodeId connection; - UA_StatusCode retVal = UA_Server_addPubSubConnection(server, &connectionConf, &connection); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - retVal |= UA_Server_getPubSubConnectionConfig(server, connection, &connectionConfig); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - ck_assert(connectionConfig.connectionProperties.mapSize == connectionConf.connectionProperties.mapSize); - ck_assert(UA_String_equal(&connectionConfig.name, &connectionConf.name) == UA_TRUE); - ck_assert(UA_String_equal(&connectionConfig.transportProfileUri, &connectionConf.transportProfileUri) == UA_TRUE); - UA_NetworkAddressUrlDataType networkAddressUrlDataCopy = *((UA_NetworkAddressUrlDataType *)connectionConfig.address.data); - ck_assert(UA_calcSizeBinary(&networkAddressUrlDataCopy, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL) == - UA_calcSizeBinary(&networkAddressUrlData, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE], NULL)); - for(size_t i = 0; i < connectionConfig.connectionProperties.mapSize; i++){ - ck_assert(UA_String_equal(&connectionConfig.connectionProperties.map[i].key.name, &connectionConf.connectionProperties.map[i].key.name) == UA_TRUE); - ck_assert(UA_Variant_calcSizeBinary(&connectionConfig.connectionProperties.map[i].value) == UA_Variant_calcSizeBinary(&connectionConf.connectionProperties.map[i].value)); - } - UA_PubSubConnectionConfig_clear(&connectionConfig); -} END_TEST - -int main(int argc, char **argv) { - if (argc < 2) { - usage(argv[0]); - return EXIT_SUCCESS; - } - else { - if (strcmp(argv[1], "-h") == 0) { - usage(argv[0]); - return EXIT_SUCCESS; - } - ethernetInterface = UA_STRING(argv[1]); - } - - TCase *tc_add_pubsub_connections_minimal_config = tcase_create("Create PubSub XDP Connections with minimal valid config"); - tcase_add_checked_fixture(tc_add_pubsub_connections_minimal_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_minimal_config, AddConnectionsWithMinimalValidConfiguration); - tcase_add_test(tc_add_pubsub_connections_minimal_config, AddRemoveAddConnectionWithMinimalValidConfiguration); - - TCase *tc_add_pubsub_connections_invalid_config = tcase_create("Create PubSub XDP Connections with invalid configurations"); - tcase_add_checked_fixture(tc_add_pubsub_connections_invalid_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_minimal_config, AddConnectionsWithInvalidConfiguration); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithInvalidAddress); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithInvalidInterface); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithUnknownTransportURL); - tcase_add_test(tc_add_pubsub_connections_invalid_config, AddConnectionWithNullConfig); - - TCase *tc_add_pubsub_connections_maximal_config = tcase_create("Create PubSub XDP Connections with maximal valid config"); - tcase_add_checked_fixture(tc_add_pubsub_connections_maximal_config, setup, teardown); - tcase_add_test(tc_add_pubsub_connections_maximal_config, AddSingleConnectionWithMaximalConfiguration); - tcase_add_test(tc_add_pubsub_connections_maximal_config, GetMaximalConnectionConfigurationAndCompareValues); - - Suite *s = suite_create("PubSub XDP connection creation"); - suite_add_tcase(s, tc_add_pubsub_connections_minimal_config); - suite_add_tcase(s, tc_add_pubsub_connections_invalid_config); - suite_add_tcase(s, tc_add_pubsub_connections_maximal_config); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} - - diff --git a/tests/pubsub/attic/check_pubsub_publish_ethernet_etf.c b/tests/pubsub/attic/check_pubsub_publish_ethernet_etf.c deleted file mode 100644 index f8fae45c8..000000000 --- a/tests/pubsub/attic/check_pubsub_publish_ethernet_etf.c +++ /dev/null @@ -1,202 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2019 Kalycito Infotech Private Limited - */ - -#include -#include - -#include -#include -#include - -#include "test_helpers.h" -#include "ua_pubsub.h" -#include "ua_server_internal.h" - -/* Adjust your configuration globally for the ethernet tests here: */ -#include "ethernet_config.h" - -#define PUBLISHING_MULTICAST_MAC_ADDRESS1 "opc.eth://01-00-5E-7F-00-01" -#define PUBLISHING_MULTICAST_MAC_ADDRESS2 "opc.eth://01-00-5E-7F-00-01:8.4" -#define BUFFER_STRING "Hello! This is Ethernet ETF Testing" -#define TRANSPORT_PROFILE_URI "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp" -#define CONNECTION_NAME "ETF Ethernet Test Connection" -#define UA_SUBSCRIBER_PORT 4801 /* Port for Subscriber*/ -#define PUBLISHER_ID 2234 -#define CYCLE_TIME 0.25 -#define SECONDS 1000 * 1000 * 1000 -#define MILLI_SECONDS 1000 * 1000 -#define SECONDS_SLEEP 5 -#define NANO_SECONDS_SLEEP_PUB (long) (CYCLE_TIME * MILLI_SECONDS * 0.6) -#define QBV_OFFSET 25 * 1000 -#define CLOCKID CLOCK_TAI -#define SOCKET_PRIORITY 3 - -/* Global declaration for test cases */ -UA_Server *server = NULL; -UA_ServerConfig *config = NULL; -UA_NodeId connection_test; -UA_PubSubConnection *connection; /* setup() is to create an environment for test cases */ - -static void setup(void) { - /*Add setup by creating new server with valid configuration */ - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - config = UA_Server_getConfig(server); - UA_ServerConfig_setMinimal(config, UA_SUBSCRIBER_PORT, NULL); - UA_Server_run_startup(server); -} - -/* teardown() is to delete the environment set for test cases */ -static void teardown(void) { - /*Call server delete functions */ - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -START_TEST(EthernetSendWithoutVLANTag) { - /* Add connection to the server */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING(CONNECTION_NAME); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(PUBLISHING_MULTICAST_MAC_ADDRESS1)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_Server_addPubSubConnection(server, &connectionConfig, &connection_test); - connection = UA_PubSubConnection_findConnectionbyId(server, connection_test); - ck_assert(connection); - - /* Add a writer group to enable the connection */ - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - UA_NodeId localWriterGroup; - UA_StatusCode retVal = - UA_Server_addWriterGroup(server, connection_test, - &writerGroupConfig, &localWriterGroup); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - retVal = UA_Server_enableWriterGroup(server, localWriterGroup); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* TODO: Encapsulate ETF config in transportSettings */ - /* UA_ExtensionObject transportSettings; */ - /* memset(&transportSettings, 0, sizeof(UA_ExtensionObject)); */ - /* transportSettings.encoding = UA_EXTENSIONOBJECT_DECODED; */ - /* transportSettings.content.decoded.data = ðernettransportSettings; */ - /* clock_gettime(CLOCKID, &nextnanosleeptime); */ - /* transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET; */ - /* ethernettransportSettings.transmission_time = transmission_time; */ - - /* Initialize a buffer to send data */ - UA_ByteString testBuffer = UA_STRING(BUFFER_STRING); - UA_ByteString networkBuffer = UA_STRING_NULL; - connection->cm->allocNetworkBuffer(connection->cm, - connection->sendChannel, - &networkBuffer, - testBuffer.length); - memcpy(networkBuffer.data, testBuffer.data, testBuffer.length); - - retVal = connection->cm-> - sendWithConnection(connection->cm, connection->sendChannel, - &UA_KEYVALUEMAP_NULL, &networkBuffer); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(EthernetSendWithVLANTag) { - /* Add connection to the server */ - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING(CONNECTION_NAME); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING(ETHERNET_INTERFACE), UA_STRING(PUBLISHING_MULTICAST_MAC_ADDRESS2)}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING(TRANSPORT_PROFILE_URI); - connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; - connectionConfig.publisherId.id.uint16 = PUBLISHER_ID; - /* Connection options are given as Key/Value Pairs - Sockprio and Txtime */ - UA_KeyValuePair connectionOptions[2]; - connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority"); - UA_UInt32 sockPriority = SOCKET_PRIORITY; - UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]); - connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime"); - UA_Boolean enableTxTime = UA_TRUE; - UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]); - connectionConfig.connectionProperties.map = connectionOptions; - connectionConfig.connectionProperties.mapSize = 2; - UA_Server_addPubSubConnection(server, &connectionConfig, &connection_test); - connection = UA_PubSubConnection_findConnectionbyId(server, connection_test); - ck_assert(connection); - - /* Add a writer group to enable the connection */ - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - UA_NodeId localWriterGroup; - UA_StatusCode retVal = - UA_Server_addWriterGroup(server, connection_test, - &writerGroupConfig, &localWriterGroup); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - retVal = UA_Server_setWriterGroupOperational(server, localWriterGroup); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - /* TODO: Encapsulate ETF config in transportSettings */ - /* UA_ExtensionObject transportSettings; */ - /* memset(&transportSettings, 0, sizeof(UA_ExtensionObject)); */ - /* transportSettings.encoding = UA_EXTENSIONOBJECT_DECODED; */ - /* transportSettings.content.decoded.data = ðernettransportSettings; */ - /* clock_gettime(CLOCKID, &nextnanosleeptime); */ - /* transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET; */ - /* ethernettransportSettings.transmission_time = transmission_time; */ - - /* Initialize a buffer to send data */ - UA_ByteString testBuffer = UA_STRING(BUFFER_STRING); - UA_ByteString networkBuffer = UA_STRING_NULL; - connection->cm->allocNetworkBuffer(connection->cm, - connection->sendChannel, - &networkBuffer, - testBuffer.length); - memcpy(networkBuffer.data, testBuffer.data, testBuffer.length); - - retVal = connection->cm->sendWithConnection(connection->cm, connection->sendChannel, - &UA_KEYVALUEMAP_NULL, &networkBuffer); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -int main(void) { - if(SKIP_ETHERNET && strlen(SKIP_ETHERNET) > 0) - return EXIT_SUCCESS; - - /*Test case to run both publisher*/ - TCase *tc_pubsub_ethernet_etf_publish = tcase_create("Publisher publishing Ethernet packets based on etf"); - tcase_add_checked_fixture(tc_pubsub_ethernet_etf_publish, setup, teardown); - tcase_add_test(tc_pubsub_ethernet_etf_publish, EthernetSendWithoutVLANTag); - tcase_add_test(tc_pubsub_ethernet_etf_publish, EthernetSendWithVLANTag); - Suite *suite = suite_create("Ethernet ETF Publisher"); - suite_add_tcase(suite, tc_pubsub_ethernet_etf_publish); - SRunner *suiteRunner = srunner_create(suite); - srunner_set_fork_status(suiteRunner, CK_NOFORK); - srunner_run_all(suiteRunner,CK_NORMAL); - int number_failed = srunner_ntests_failed(suiteRunner); - srunner_free(suiteRunner); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} From 4adf6a8b8b0dac3cb8735bb4c5e5d487ffb230b6 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Jan 2025 19:32:32 +0100 Subject: [PATCH 155/158] refactor(pubsub): Remove unneeded freeze flags --- src/pubsub/ua_pubsub_internal.h | 5 +- src/pubsub/ua_pubsub_readergroup.c | 24 -- src/pubsub/ua_pubsub_writergroup.c | 24 -- tests/CMakeLists.txt | 2 - tests/pubsub/check_pubsub_config_freeze.c | 383 ------------------ .../check_pubsub_subscribe_config_freeze.c | 276 ------------- 6 files changed, 3 insertions(+), 711 deletions(-) delete mode 100644 tests/pubsub/check_pubsub_config_freeze.c delete mode 100644 tests/pubsub/check_pubsub_subscribe_config_freeze.c diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index 950e902a0..f5198b860 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -184,6 +184,9 @@ typedef struct UA_PublishedDataSet { UA_DataSetMetaDataType dataSetMetaData; UA_UInt16 fieldSize; UA_UInt16 promotedFieldsCount; + + /* The counter is required because the PDS has not state. + * Check if it is actively used when changes are introduced. */ UA_UInt16 configurationFreezeCounter; } UA_PublishedDataSet; @@ -367,7 +370,6 @@ struct UA_WriterGroup { UA_UInt64 publishCallbackId; /* registered if != 0 */ UA_UInt16 sequenceNumber; /* Increased after every sent message */ - UA_Boolean configurationFrozen; UA_DateTime lastPublishTimeStamp; /* The ConnectionManager pointer is stored in the Connection. The channels @@ -512,7 +514,6 @@ struct UA_ReaderGroup { LIST_HEAD(, UA_DataSetReader) readers; UA_UInt32 readersCount; - UA_Boolean configurationFrozen; UA_Boolean hasReceived; /* Received a message since the last _connect */ /* The ConnectionManager pointer is stored in the Connection. The channels diff --git a/src/pubsub/ua_pubsub_readergroup.c b/src/pubsub/ua_pubsub_readergroup.c index b6a9637d9..baa132a49 100644 --- a/src/pubsub/ua_pubsub_readergroup.c +++ b/src/pubsub/ua_pubsub_readergroup.c @@ -209,18 +209,6 @@ UA_ReaderGroup_remove(UA_PubSubManager *psm, UA_ReaderGroup *rg) { UA_PubSubConnection_setPubSubState(psm, connection, connection->head.state); } -static UA_StatusCode -UA_ReaderGroup_freezeConfiguration(UA_PubSubManager *psm, UA_ReaderGroup *rg) { - UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); - rg->configurationFrozen = true; - return UA_STATUSCODE_GOOD; -} - -static void -UA_ReaderGroup_unfreezeConfiguration(UA_ReaderGroup *rg) { - rg->configurationFrozen = false; -} - UA_StatusCode UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, UA_PubSubState targetState) { @@ -247,11 +235,6 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, ret = rg->config.customStateMachine(server, rg->head.identifier, rg->config.context, &rg->head.state, targetState); UA_LOCK(&server->serviceMutex); - if(rg->head.state == UA_PUBSUBSTATE_DISABLED || - rg->head.state == UA_PUBSUBSTATE_ERROR) - UA_ReaderGroup_unfreezeConfiguration(rg); - else - UA_ReaderGroup_freezeConfiguration(psm, rg); goto finalize_state_machine; } @@ -261,7 +244,6 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, case UA_PUBSUBSTATE_DISABLED: case UA_PUBSUBSTATE_ERROR: rg->head.state = targetState; - UA_ReaderGroup_unfreezeConfiguration(rg); UA_ReaderGroup_disconnect(rg); rg->hasReceived = false; break; @@ -270,11 +252,6 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, case UA_PUBSUBSTATE_PAUSED: case UA_PUBSUBSTATE_PREOPERATIONAL: case UA_PUBSUBSTATE_OPERATIONAL: - /* Freeze the configuration */ - ret = UA_ReaderGroup_freezeConfiguration(psm, rg); - if(ret != UA_STATUSCODE_GOOD) - break; - if(psm->sc.state != UA_LIFECYCLESTATE_STARTED) { /* Avoid repeat warnings */ if(oldState != UA_PUBSUBSTATE_PAUSED) { @@ -312,7 +289,6 @@ UA_ReaderGroup_setPubSubState(UA_PubSubManager *psm, UA_ReaderGroup *rg, /* Failure */ if(ret != UA_STATUSCODE_GOOD) { rg->head.state = UA_PUBSUBSTATE_ERROR; - UA_ReaderGroup_unfreezeConfiguration(rg); UA_ReaderGroup_disconnect(rg); rg->hasReceived = false; } diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index e48d3c5e6..c3ae14595 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -266,18 +266,6 @@ UA_WriterGroup_remove(UA_PubSubManager *psm, UA_WriterGroup *wg) { UA_PubSubConnection_setPubSubState(psm, connection, connection->head.state); } -static UA_StatusCode -UA_WriterGroup_freezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { - UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); - wg->configurationFrozen = true; - return UA_STATUSCODE_GOOD; -} - -static void -UA_WriterGroup_unfreezeConfiguration(UA_PubSubManager *psm, UA_WriterGroup *wg) { - wg->configurationFrozen = false; -} - UA_StatusCode UA_WriterGroupConfig_copy(const UA_WriterGroupConfig *src, UA_WriterGroupConfig *dst) { @@ -386,11 +374,6 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, ret = wg->config.customStateMachine(server, wg->head.identifier, wg->config.context, &wg->head.state, targetState); UA_LOCK(&server->serviceMutex); - if(wg->head.state == UA_PUBSUBSTATE_DISABLED || - wg->head.state == UA_PUBSUBSTATE_ERROR) - UA_WriterGroup_unfreezeConfiguration(psm, wg); - else - UA_WriterGroup_freezeConfiguration(psm, wg); goto finalize_state_machine; } @@ -400,7 +383,6 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, case UA_PUBSUBSTATE_DISABLED: case UA_PUBSUBSTATE_ERROR: wg->head.state = targetState; - UA_WriterGroup_unfreezeConfiguration(psm, wg); UA_WriterGroup_disconnect(wg); UA_WriterGroup_removePublishCallback(psm, wg); break; @@ -409,11 +391,6 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, case UA_PUBSUBSTATE_PAUSED: case UA_PUBSUBSTATE_PREOPERATIONAL: case UA_PUBSUBSTATE_OPERATIONAL: - /* Freeze the configuration */ - ret = UA_WriterGroup_freezeConfiguration(psm, wg); - if(ret != UA_STATUSCODE_GOOD) - break; - /* PAUSED has no open connections and periodic callbacks */ if(psm->sc.state != UA_LIFECYCLESTATE_STARTED) { /* Avoid repeat warnings */ @@ -463,7 +440,6 @@ UA_WriterGroup_setPubSubState(UA_PubSubManager *psm, UA_WriterGroup *wg, /* Failure */ if(ret != UA_STATUSCODE_GOOD) { wg->head.state = UA_PUBSUBSTATE_ERROR;; - UA_WriterGroup_unfreezeConfiguration(psm, wg); UA_WriterGroup_disconnect(wg); UA_WriterGroup_removePublishCallback(psm, wg); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be1b15b20..9300d3062 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -271,8 +271,6 @@ if(UA_ENABLE_PUBSUB) #Link libraries for executing subscriber unit test ua_add_test(pubsub/check_pubsub_subscribe.c) ua_add_test(pubsub/check_pubsub_publishspeed.c) - ua_add_test(pubsub/check_pubsub_config_freeze.c) - ua_add_test(pubsub/check_pubsub_subscribe_config_freeze.c) ua_add_test(pubsub/check_pubsub_offset.c) if(UA_ARCHITECTURE_POSIX) diff --git a/tests/pubsub/check_pubsub_config_freeze.c b/tests/pubsub/check_pubsub_config_freeze.c deleted file mode 100644 index 37569d080..000000000 --- a/tests/pubsub/check_pubsub_config_freeze.c +++ /dev/null @@ -1,383 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2019 Fraunhofer IOSB (Author: Andreas Ebner) - */ - -#include -#include - -#include "ua_server_internal.h" -#include "ua_pubsub_internal.h" -#include "test_helpers.h" - -#include -#include -#include -#include - -UA_Server *server = NULL; - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -START_TEST(CreateAndLockConfiguration) { - //create config - UA_NodeId connection1, writerGroup1, publishedDataSet1, dataSetWriter1, dataSetField1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); - - UA_PublishedDataSetConfig pdsConfig; - memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig)); - pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - pdsConfig.name = UA_STRING("PublishedDataSet 1"); - retVal |= UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1).addResult; - - UA_DataSetFieldConfig fieldConfig; - memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.publishParameters.publishedVariable = - UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE); - retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &dataSetField1).result; - - UA_PubSubManager *psm = getPSM(server); - - UA_WriterGroup *writerGroup = UA_WriterGroup_find(psm, writerGroup1); - ck_assert(writerGroup->head.state == UA_PUBSUBSTATE_DISABLED); - - UA_DataSetMetaDataType dataSetMetaDataType; - UA_DataSetMetaDataType_init(&dataSetMetaDataType); - ck_assert(UA_Server_getPublishedDataSetMetaData(server, publishedDataSet1, &dataSetMetaDataType) == UA_STATUSCODE_GOOD); - UA_DataSetMetaDataType_clear(&dataSetMetaDataType); - - UA_PublishedDataSetConfig publishedDataSetConfig; - memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig)); - publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS_TEMPLATE; - UA_DataSetMetaDataType metaDataType; - UA_DataSetMetaDataType_init(&metaDataType); - publishedDataSetConfig.config.itemsTemplate.metaData = metaDataType; - publishedDataSetConfig.config.itemsTemplate.variablesToAddSize = 1; - publishedDataSetConfig.config.itemsTemplate.variablesToAdd = UA_PublishedVariableDataType_new(); - UA_PublishedDataSetConfig_clear(&publishedDataSetConfig); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1); - ck_assert(retVal == UA_STATUSCODE_GOOD); - UA_DataSetWriter *dataSetWriter = UA_DataSetWriter_find(psm, dataSetWriter1); - ck_assert(dataSetWriter != NULL); - - //get internal PubSubConnection Pointer - UA_PubSubConnection *pubSubConnection = UA_PubSubConnection_find(psm, connection1); - ck_assert(pubSubConnection != NULL); - - ck_assert(dataSetWriter->configurationFrozen == UA_FALSE); - //Lock the writer group and the child pubsub entities - UA_WriterGroup *wg = UA_WriterGroup_find(psm, writerGroup1); - UA_LOCK(&psm->sc.server->serviceMutex); - UA_DataSetWriter_setPubSubState(psm, dataSetWriter, UA_PUBSUBSTATE_OPERATIONAL); - ck_assert(dataSetWriter->configurationFrozen == UA_TRUE); - UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&psm->sc.server->serviceMutex); - ck_assert(wg->configurationFrozen == UA_TRUE); - UA_PublishedDataSet *publishedDataSet = dataSetWriter->connectedDataSet; - ck_assert(publishedDataSet->configurationFreezeCounter > 0); -} END_TEST - -START_TEST(CreateAndLockConfigurationWithExternalAPI) { - //create config - UA_NodeId connection1, writerGroup1, publishedDataSet1, dataSetWriter1, dataSetField1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); - - UA_PublishedDataSetConfig pdsConfig; - memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig)); - pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - pdsConfig.name = UA_STRING("PublishedDataSet 1"); - retVal |= UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1).addResult; - - UA_DataSetFieldConfig fieldConfig; - memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.publishParameters.publishedVariable = - UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE); - retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &dataSetField1).result; - - UA_PubSubManager *psm = getPSM(server); - - //get internal WG Pointer - UA_WriterGroup *writerGroup = UA_WriterGroup_find(psm, writerGroup1); - ck_assert(writerGroup->head.state == UA_PUBSUBSTATE_DISABLED); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1); - ck_assert(retVal == UA_STATUSCODE_GOOD); - UA_DataSetWriter *dataSetWriter = UA_DataSetWriter_find(psm, dataSetWriter1); - ck_assert(dataSetWriter != NULL); - - UA_LOCK(&psm->sc.server->serviceMutex); - UA_DataSetWriter_setPubSubState(psm, dataSetWriter, UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&psm->sc.server->serviceMutex); - ck_assert(dataSetWriter->configurationFrozen == UA_TRUE); - - UA_PublishedDataSet *publishedDataSet = dataSetWriter->connectedDataSet; - ck_assert(publishedDataSet->configurationFreezeCounter > 0); - - //Lock the with the freeze function - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&psm->sc.server->serviceMutex); - //set state to disabled and implicit unlock the configuration - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&psm->sc.server->serviceMutex); -} END_TEST - -START_TEST(CreateAndReleaseMultiplePDSLocks) { - //create config - UA_NodeId connection1, writerGroup1, writerGroup2, publishedDataSet1, dataSetWriter1, dataSetWriter2, dataSetWriter3, dataSetField1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - //Add two writer groups - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); - writerGroupConfig.name = UA_STRING("WriterGroup 2"); - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup2); - - UA_PublishedDataSetConfig pdsConfig; - memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig)); - pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - pdsConfig.name = UA_STRING("PublishedDataSet 1"); - retVal |= UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1).addResult; - - UA_DataSetFieldConfig fieldConfig; - memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.publishParameters.publishedVariable = - UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE); - retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &dataSetField1).result; - - UA_PubSubManager *psm = getPSM(server); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 2"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter2); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 3"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup2, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter3); - - ck_assert(retVal == UA_STATUSCODE_GOOD); - - UA_WriterGroup *writerGroup_1 = UA_WriterGroup_find(psm, writerGroup1); - UA_WriterGroup *writerGroup_2 = UA_WriterGroup_find(psm, writerGroup2); - UA_PublishedDataSet *publishedDataSet = UA_PublishedDataSet_find(psm, publishedDataSet1); - - //freeze configuratoin of both WG - ck_assert(writerGroup_1->configurationFrozen == UA_FALSE); - ck_assert(writerGroup_2->configurationFrozen == UA_FALSE); - ck_assert(publishedDataSet->configurationFreezeCounter == 0); - - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_OPERATIONAL); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup2), UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&psm->sc.server->serviceMutex); - ck_assert(writerGroup_1->configurationFrozen == UA_TRUE); - ck_assert(writerGroup_2->configurationFrozen == UA_TRUE); - //unlock one tree, get sure pds still locked - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&psm->sc.server->serviceMutex); - ck_assert(writerGroup_1->configurationFrozen == UA_FALSE); - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup2), UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&psm->sc.server->serviceMutex); - ck_assert(writerGroup_2->configurationFrozen == UA_FALSE); -} END_TEST - -START_TEST(CreateLockAndEditConfiguration) { - UA_NodeId connection1, writerGroup1, publishedDataSet1, dataSetWriter1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); - - UA_PublishedDataSetConfig pdsConfig; - memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig)); - pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - pdsConfig.name = UA_STRING("PublishedDataSet 1"); - retVal |= UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1).addResult; - - UA_DataSetFieldConfig fieldConfig; - memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.publishParameters.publishedVariable = - UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE); - UA_NodeId localDataSetField; - retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result; - - //get internal WG Pointer - UA_PubSubManager *psm = getPSM(server); - UA_WriterGroup *writerGroup = UA_WriterGroup_find(psm, writerGroup1); - ck_assert(writerGroup->head.state == UA_PUBSUBSTATE_DISABLED); - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1); - UA_DataSetWriter *dataSetWriter = UA_DataSetWriter_find(psm, dataSetWriter1); - ck_assert(dataSetWriter != NULL); - retVal |= UA_Server_enableDataSetWriter(server, dataSetWriter1); - ck_assert(dataSetWriter->configurationFrozen == UA_TRUE); - - //Lock the writer group and the child pubsub entities - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&psm->sc.server->serviceMutex); - //call not allowed configuration methods - UA_DataSetFieldResult fieldRemoveResult = UA_Server_removeDataSetField(server, localDataSetField); - ck_assert(fieldRemoveResult.result == UA_STATUSCODE_BADCONFIGURATIONERROR); - ck_assert(UA_Server_removePublishedDataSet(server, publishedDataSet1) == UA_STATUSCODE_BADCONFIGURATIONERROR); - UA_LOCK(&psm->sc.server->serviceMutex); - UA_WriterGroup_setPubSubState(psm, UA_WriterGroup_find(psm, writerGroup1), UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&psm->sc.server->serviceMutex); - UA_Server_disableDataSetWriter(server, dataSetWriter1); - fieldRemoveResult = UA_Server_removeDataSetField(server, localDataSetField); - ck_assert(fieldRemoveResult.result == UA_STATUSCODE_GOOD); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(CreateConfigWithStaticFieldSource) { - UA_NodeId connection1, writerGroup1, publishedDataSet1, dataSetWriter1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_WriterGroupConfig writerGroupConfig; - memset(&writerGroupConfig, 0, sizeof(writerGroupConfig)); - writerGroupConfig.name = UA_STRING("WriterGroup 1"); - writerGroupConfig.publishingInterval = 10; - writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; - retVal |= UA_Server_addWriterGroup(server, connection1, &writerGroupConfig, &writerGroup1); - - UA_PublishedDataSetConfig pdsConfig; - memset(&pdsConfig, 0, sizeof(UA_PublishedDataSetConfig)); - pdsConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; - pdsConfig.name = UA_STRING("PublishedDataSet 1"); - retVal |= UA_Server_addPublishedDataSet(server, &pdsConfig, &publishedDataSet1).addResult; - - UA_UInt32 *intValue = UA_UInt32_new(); - UA_DataValue *dataValue = UA_DataValue_new(); - UA_Variant_setScalar(&dataValue->value, intValue, &UA_TYPES[UA_TYPES_UINT32]); - - UA_DataSetFieldConfig fieldConfig; - memset(&fieldConfig, 0, sizeof(UA_DataSetFieldConfig)); - fieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; - fieldConfig.field.variable.fieldNameAlias = UA_STRING("field 1"); - fieldConfig.field.variable.publishParameters.publishedVariable = - UA_NS0ID(SERVER_SERVERSTATUS); - UA_NodeId localDataSetField; - retVal |= UA_Server_addDataSetField(server, publishedDataSet1, &fieldConfig, &localDataSetField).result; - - UA_DataSetWriterConfig dataSetWriterConfig; - memset(&dataSetWriterConfig, 0, sizeof(dataSetWriterConfig)); - dataSetWriterConfig.name = UA_STRING("DataSetWriter 1"); - retVal |= UA_Server_addDataSetWriter(server, writerGroup1, publishedDataSet1, &dataSetWriterConfig, &dataSetWriter1); - UA_DataValue_delete(dataValue); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - } END_TEST - -int main(void) { - TCase *tc_lock_configuration = tcase_create("Create and Lock"); - tcase_add_checked_fixture(tc_lock_configuration, setup, teardown); - tcase_add_test(tc_lock_configuration, CreateAndLockConfiguration); - tcase_add_test(tc_lock_configuration, CreateAndLockConfigurationWithExternalAPI); - tcase_add_test(tc_lock_configuration, CreateAndReleaseMultiplePDSLocks); - tcase_add_test(tc_lock_configuration, CreateLockAndEditConfiguration); - tcase_add_test(tc_lock_configuration, CreateConfigWithStaticFieldSource); - - Suite *s = suite_create("PubSub RT configuration lock mechanism"); - suite_add_tcase(s, tc_lock_configuration); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/tests/pubsub/check_pubsub_subscribe_config_freeze.c b/tests/pubsub/check_pubsub_subscribe_config_freeze.c deleted file mode 100644 index a37a56634..000000000 --- a/tests/pubsub/check_pubsub_subscribe_config_freeze.c +++ /dev/null @@ -1,276 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2020 Kalycito Infotech Private Limited (Author: Suriya Narayanan) - */ - -#include -#include - -#include "ua_server_internal.h" -#include "ua_pubsub_internal.h" -#include "test_helpers.h" - -#include -#include -#include -#include - -UA_Server *server = NULL; - -static void setup(void) { - server = UA_Server_newForUnitTest(); - ck_assert(server != NULL); - UA_Server_run_startup(server); -} - -static void teardown(void) { - UA_Server_run_shutdown(server); - UA_Server_delete(server); -} - -START_TEST(CreateAndLockConfiguration) { - //create config - UA_NodeId connection1, readerGroup1, dataSetReader1; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_ReaderGroupConfig readerGroupConfig; - memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); - - UA_PubSubManager *psm = getPSM(server); - - //get internal RG Pointer - UA_ReaderGroup *readerGroup = UA_ReaderGroup_find(psm, readerGroup1); - ck_assert(readerGroup->head.state == UA_PUBSUBSTATE_DISABLED); - ck_assert(readerGroup->configurationFrozen == UA_FALSE); - - UA_DataSetReaderConfig dataSetReaderConfig; - memset(&dataSetReaderConfig, 0, sizeof(dataSetReaderConfig)); - dataSetReaderConfig.name = UA_STRING("DataSetReader 1"); - retVal |= UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader1); - UA_DataSetReader *dataSetReader = UA_DataSetReader_find(psm, dataSetReader1); - ck_assert(dataSetReader != NULL); - - //Lock the reader group and the child pubsub entities - UA_ReaderGroup *readerGroup_1 = UA_ReaderGroup_find(psm, readerGroup1); - UA_LOCK(&server->serviceMutex); - retVal |= UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_1, UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&server->serviceMutex); - - ck_assert(readerGroup->configurationFrozen == UA_TRUE); - - //set state to disabled and implicit unlock the configuration - UA_LOCK(&server->serviceMutex); - retVal |= UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_1, UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&server->serviceMutex); - - ck_assert(readerGroup->configurationFrozen == UA_FALSE); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); -} END_TEST - -START_TEST(CreateAndReleaseMultipleLocks) { - //create config - UA_NodeId connection1, readerGroup1, readerGroup2, dataSetReader1, dataSetReader2, dataSetReader3; - UA_PubSubConnectionConfig connectionConfig; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - //Add two reader groups - UA_ReaderGroupConfig readerGroupConfig; - memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); - readerGroupConfig.name = UA_STRING("ReaderGroup 2"); - retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup2); - - UA_DataSetReaderConfig dataSetReaderConfig; - memset(&dataSetReaderConfig, 0, sizeof(dataSetReaderConfig)); - dataSetReaderConfig.name = UA_STRING("DataSetReader 1"); - retVal |= UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader1); - dataSetReaderConfig.name = UA_STRING("DataSetReader 2"); - retVal |= UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader2); - dataSetReaderConfig.name = UA_STRING("DataSetReader 3"); - retVal |= UA_Server_addDataSetReader(server, readerGroup2, &dataSetReaderConfig, &dataSetReader3); - - UA_PubSubManager *psm = getPSM(server); - UA_ReaderGroup *readerGroup_1 = UA_ReaderGroup_find(psm, readerGroup1); - UA_ReaderGroup *readerGroup_2 = UA_ReaderGroup_find(psm, readerGroup2); - - //freeze configuration of both RG - ck_assert(readerGroup_1->configurationFrozen == UA_FALSE); - ck_assert(readerGroup_2->configurationFrozen == UA_FALSE); - - UA_LOCK(&server->serviceMutex); - retVal |= UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_1, UA_PUBSUBSTATE_OPERATIONAL); - retVal |= UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_2, UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&server->serviceMutex); - ck_assert_int_eq(retVal, UA_STATUSCODE_GOOD); - - ck_assert(readerGroup_1->configurationFrozen == UA_TRUE); - ck_assert(readerGroup_2->configurationFrozen == UA_TRUE); - - //unlock one tree, get sure connection still locked - UA_LOCK(&server->serviceMutex); - UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_1, UA_PUBSUBSTATE_DISABLED); - UA_ReaderGroup_setPubSubState(getPSM(server), readerGroup_2, UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&server->serviceMutex); - ck_assert(readerGroup_1->configurationFrozen == UA_FALSE); - ck_assert(readerGroup_2->configurationFrozen == UA_FALSE); -} END_TEST - -START_TEST(CreateLockAndEditConfiguration) { - UA_NodeId connection1, readerGroup1, dataSetReader1, dataSetReader2; - UA_StatusCode retVal = UA_STATUSCODE_GOOD; - UA_PubSubConnectionConfig connectionConfig; - memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); - connectionConfig.name = UA_STRING("UADP Connection"); - UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")}; - UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, - &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); - connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); - retVal |= UA_Server_addPubSubConnection(server, &connectionConfig, &connection1); - - UA_ReaderGroupConfig readerGroupConfig; - memset(&readerGroupConfig, 0, sizeof(readerGroupConfig)); - readerGroupConfig.name = UA_STRING("ReaderGroup 1"); - retVal |= UA_Server_addReaderGroup(server, connection1, &readerGroupConfig, &readerGroup1); - - UA_PubSubManager *psm = getPSM(server); - - //get internal RG Pointer - UA_ReaderGroup *readerGroup = UA_ReaderGroup_find(psm, readerGroup1); - ck_assert(readerGroup->head.state == UA_PUBSUBSTATE_DISABLED); - ck_assert(readerGroup->configurationFrozen == UA_FALSE); - - UA_DataSetReaderConfig dataSetReaderConfig; - memset(&dataSetReaderConfig, 0, sizeof(dataSetReaderConfig)); - dataSetReaderConfig.name = UA_STRING("DataSetReader 1"); - /* Setting up Meta data configuration in DataSetReader for DateTime DataType */ - UA_DataSetMetaDataType *pMetaData = &dataSetReaderConfig.dataSetMetaData; - /* FilltestMetadata function in subscriber implementation */ - UA_DataSetMetaDataType_init(pMetaData); - pMetaData->name = UA_STRING("DataSet Test"); - /* Static definition of number of fields size to 1 to create one - targetVariable */ - pMetaData->fieldsSize = 1; - pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, - &UA_TYPES[UA_TYPES_FIELDMETADATA]); - /* DateTime DataType */ - UA_FieldMetaData_init (&pMetaData->fields[0]); - UA_NodeId_copy (&UA_TYPES[UA_TYPES_DATETIME].typeId, - &pMetaData->fields[0].dataType); - pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; - pMetaData->fields[0].name = UA_STRING ("DateTime"); - pMetaData->fields[0].valueRank = -1; /* scalar */ - retVal |= UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader1); - - UA_NodeId folderId; - UA_String folderName = dataSetReaderConfig.dataSetMetaData.name; - UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; - UA_QualifiedName folderBrowseName; - if(folderName.length > 0) { - oAttr.displayName.locale = UA_STRING ("en-US"); - oAttr.displayName.text = folderName; - folderBrowseName.namespaceIndex = 1; - folderBrowseName.name = folderName; - } - else { - oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); - folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); - } - - retVal |= UA_Server_addObjectNode (server, UA_NODEID_NULL, - UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER), - UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES), - folderBrowseName, UA_NODEID_NUMERIC (0, - UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId); - - UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) - UA_calloc(dataSetReaderConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); - for(size_t i = 0; i < dataSetReaderConfig.dataSetMetaData.fieldsSize; i++) { - /* Variable to subscribe data */ - UA_VariableAttributes vAttr = UA_VariableAttributes_default; - UA_LocalizedText_copy(&dataSetReaderConfig.dataSetMetaData.fields[i].description, - &vAttr.description); - vAttr.displayName.locale = UA_STRING("en-US"); - vAttr.displayName.text = dataSetReaderConfig.dataSetMetaData.fields[i].name; - vAttr.dataType = dataSetReaderConfig.dataSetMetaData.fields[i].dataType; - - UA_NodeId newNode; - retVal |= UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), - folderId, - UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), - UA_QUALIFIEDNAME(1, (char *)dataSetReaderConfig.dataSetMetaData.fields[i].name.data), - UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), - vAttr, NULL, &newNode); - targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; - targetVars[i].targetNodeId = newNode; - } - - retVal = UA_Server_enableDataSetReader(server, dataSetReader1); - ck_assert(retVal == UA_STATUSCODE_GOOD); - - //Lock the reader group and the child pubsub entities - UA_ReaderGroup *rg1 = UA_ReaderGroup_find(psm, readerGroup1); - UA_LOCK(&server->serviceMutex); - UA_ReaderGroup_setPubSubState(getPSM(server), rg1, UA_PUBSUBSTATE_OPERATIONAL); - UA_UNLOCK(&server->serviceMutex); - //call not allowed configuration methods - retVal = UA_Server_DataSetReader_createTargetVariables(server, dataSetReader1, - dataSetReaderConfig.dataSetMetaData.fieldsSize, - targetVars); - ck_assert(retVal == UA_STATUSCODE_BADCONFIGURATIONERROR); - - //unlock the reader group - UA_LOCK(&server->serviceMutex); - UA_ReaderGroup_setPubSubState(getPSM(server), rg1, UA_PUBSUBSTATE_DISABLED); - UA_UNLOCK(&server->serviceMutex); - - retVal = UA_Server_disableDataSetReader(server, dataSetReader1); - ck_assert(retVal == UA_STATUSCODE_GOOD); - - retVal = UA_Server_DataSetReader_createTargetVariables(server, dataSetReader1, - dataSetReaderConfig.dataSetMetaData.fieldsSize, - targetVars); - ck_assert(retVal == UA_STATUSCODE_GOOD); - retVal = UA_Server_addDataSetReader(server, readerGroup1, &dataSetReaderConfig, &dataSetReader2); - UA_free(targetVars); - UA_free(dataSetReaderConfig.dataSetMetaData.fields); - ck_assert(retVal == UA_STATUSCODE_GOOD); - retVal = UA_Server_removeDataSetReader(server, dataSetReader1); - ck_assert(retVal == UA_STATUSCODE_GOOD); -} END_TEST - -int main(void) { - TCase *tc_lock_configuration = tcase_create("Create and Lock"); - tcase_add_checked_fixture(tc_lock_configuration, setup, teardown); - tcase_add_test(tc_lock_configuration, CreateAndLockConfiguration); - tcase_add_test(tc_lock_configuration, CreateAndReleaseMultipleLocks); - tcase_add_test(tc_lock_configuration, CreateLockAndEditConfiguration); - - Suite *s = suite_create("PubSub subscriber configuration lock mechanism"); - suite_add_tcase(s, tc_lock_configuration); - - SRunner *sr = srunner_create(s); - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr,CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} From 4af983ebaa179b9b09305e309c14b715c3789ebe Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Jan 2025 19:41:25 +0100 Subject: [PATCH 156/158] refactor(pubsub): Remove unneeded UA_DataSetWriter_prepareDataSet --- src/pubsub/ua_pubsub_internal.h | 4 --- src/pubsub/ua_pubsub_writer.c | 49 ------------------------------ src/pubsub/ua_pubsub_writergroup.c | 2 +- 3 files changed, 1 insertion(+), 54 deletions(-) diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index f5198b860..4f88ddaf3 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -342,10 +342,6 @@ UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm, UA_DataSetMessage *dsm, UA_DataSetWriter *dsw); -UA_StatusCode -UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw, - UA_DataSetMessage *dsm); - UA_StatusCode UA_DataSetWriter_create(UA_PubSubManager *psm, const UA_NodeId writerGroup, const UA_NodeId dataSet, diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index 0dfbc79a8..cd93bfb0d 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -259,55 +259,6 @@ UA_DataSetWriter_create(UA_PubSubManager *psm, return res; } -UA_StatusCode -UA_DataSetWriter_prepareDataSet(UA_PubSubManager *psm, UA_DataSetWriter *dsw, - UA_DataSetMessage *dsm) { - /* No PublishedDataSet defined -> Heartbeat messages only */ - UA_StatusCode res = UA_STATUSCODE_GOOD; - UA_PublishedDataSet *pds = dsw->connectedDataSet; - if(!pds) { - res = UA_DataSetWriter_generateDataSetMessage(psm, dsm, dsw); - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "Heartbeat DataSetMessage creation failed"); - } - return res; - } - - UA_WriterGroup *wg = dsw->linkedWriterGroup; - UA_assert(wg); - - /* Test the DataSetFields */ - UA_DataSetField *dsf; - TAILQ_FOREACH(dsf, &pds->fields, listEntry) { - UA_NodeId *publishedVariable = - &dsf->config.field.variable.publishParameters.publishedVariable; - - /* Check that the target is a VariableNode */ - const UA_VariableNode *rtNode = (const UA_VariableNode*) - UA_NODESTORE_GET(psm->sc.server, publishedVariable); - if(rtNode && rtNode->head.nodeClass != UA_NODECLASS_VARIABLE) { - UA_LOG_ERROR_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "PDS points to a node that is not a variable"); - UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *)rtNode); - return UA_STATUSCODE_BADNOTSUPPORTED; - } - UA_NODESTORE_RELEASE(psm->sc.server, (const UA_Node *)rtNode); - } - - /* Generate the DSM */ - res = UA_DataSetWriter_generateDataSetMessage(psm, dsm, dsw); - if(res != UA_STATUSCODE_GOOD) { - UA_LOG_WARNING_PUBSUB(psm->logging, dsw, - "PubSub-RT configuration fail: " - "DataSetMessage buffering failed"); - } - - return res; -} - UA_StatusCode UA_DataSetWriter_remove(UA_PubSubManager *psm, UA_DataSetWriter *dsw) { UA_LOCK_ASSERT(&psm->sc.server->serviceMutex); diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index c3ae14595..7183c0000 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -1379,7 +1379,7 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, memset(dsmStore, 0, sizeof(UA_DataSetMessage) * wg->writersCount); LIST_FOREACH(dsw, &wg->writers, listEntry) { dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; - res = UA_DataSetWriter_prepareDataSet(psm, dsw, &dsmStore[dsmCount]); + res = UA_DataSetWriter_generateDataSetMessage(psm, &dsmStore[dsmCount], dsw); dsmCount++; if(res != UA_STATUSCODE_GOOD) goto cleanup; From 2f982597ed5a45f60152b7e4e3d17e33bdbef758 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Jan 2025 19:47:30 +0100 Subject: [PATCH 157/158] refactor(pubsub): Reorder arguments of UA_DataSetWriter_generateDataSetMessage --- src/pubsub/ua_pubsub_internal.h | 4 ++-- src/pubsub/ua_pubsub_writer.c | 4 ++-- src/pubsub/ua_pubsub_writergroup.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pubsub/ua_pubsub_internal.h b/src/pubsub/ua_pubsub_internal.h index 4f88ddaf3..510ecbfad 100644 --- a/src/pubsub/ua_pubsub_internal.h +++ b/src/pubsub/ua_pubsub_internal.h @@ -339,8 +339,8 @@ UA_DataSetWriter_setPubSubState(UA_PubSubManager *psm, UA_DataSetWriter *dsw, UA_StatusCode UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm, - UA_DataSetMessage *dsm, - UA_DataSetWriter *dsw); + UA_DataSetWriter *dsw, + UA_DataSetMessage *dsm); UA_StatusCode UA_DataSetWriter_create(UA_PubSubManager *psm, diff --git a/src/pubsub/ua_pubsub_writer.c b/src/pubsub/ua_pubsub_writer.c index cd93bfb0d..13d9c4f83 100644 --- a/src/pubsub/ua_pubsub_writer.c +++ b/src/pubsub/ua_pubsub_writer.c @@ -523,8 +523,8 @@ UA_PubSubDataSetWriter_generateDeltaFrameMessage(UA_PubSubManager *psm, /* Generate a DataSetMessage for the given writer. */ UA_StatusCode UA_DataSetWriter_generateDataSetMessage(UA_PubSubManager *psm, - UA_DataSetMessage *dataSetMessage, - UA_DataSetWriter *dsw) { + UA_DataSetWriter *dsw, + UA_DataSetMessage *dataSetMessage) { UA_EventLoop *el = psm->sc.server->config.eventLoop; /* Heartbeat message if no pds is connected */ diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 7183c0000..44b6d9b46 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -839,7 +839,7 @@ UA_WriterGroup_publishCallback(UA_PubSubManager *psm, UA_WriterGroup *wg) { /* Generate the DSM */ dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; UA_StatusCode res = - UA_DataSetWriter_generateDataSetMessage(psm, &dsmStore[dsmCount], dsw); + UA_DataSetWriter_generateDataSetMessage(psm, dsw, &dsmStore[dsmCount]); if(res != UA_STATUSCODE_GOOD) { UA_LOG_ERROR_PUBSUB(psm->logging, dsw, "PubSub Publish: DataSetMessage creation failed"); @@ -1379,7 +1379,7 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, memset(dsmStore, 0, sizeof(UA_DataSetMessage) * wg->writersCount); LIST_FOREACH(dsw, &wg->writers, listEntry) { dsWriterIds[dsmCount] = dsw->config.dataSetWriterId; - res = UA_DataSetWriter_generateDataSetMessage(psm, &dsmStore[dsmCount], dsw); + res = UA_DataSetWriter_generateDataSetMessage(psm, dsw, &dsmStore[dsmCount]); dsmCount++; if(res != UA_STATUSCODE_GOOD) goto cleanup; From 95e68107417492b883dab790b687ebd63c8799c7 Mon Sep 17 00:00:00 2001 From: Julius Pfrommer Date: Sun, 19 Jan 2025 21:13:16 +0100 Subject: [PATCH 158/158] fix(pubsub): Add missing UA_LOCK in UA_Server_computeWriterGroupOffsetTable --- src/pubsub/ua_pubsub_writergroup.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pubsub/ua_pubsub_writergroup.c b/src/pubsub/ua_pubsub_writergroup.c index 44b6d9b46..571dd909e 100644 --- a/src/pubsub/ua_pubsub_writergroup.c +++ b/src/pubsub/ua_pubsub_writergroup.c @@ -1357,11 +1357,18 @@ UA_StatusCode UA_Server_computeWriterGroupOffsetTable(UA_Server *server, const UA_NodeId writerGroupId, UA_PubSubOffsetTable *ot) { + if(!server || !ot) + return UA_STATUSCODE_BADINVALIDARGUMENT; + + UA_LOCK(&server->serviceMutex); + /* Get the Writer Group */ UA_PubSubManager *psm = getPSM(server); UA_WriterGroup *wg = (psm) ? UA_WriterGroup_find(psm, writerGroupId) : NULL; - if(!wg) + if(!wg) { + UA_UNLOCK(&server->serviceMutex); return UA_STATUSCODE_BADNOTFOUND; + } /* Initialize variables so we can goto cleanup below */ UA_DataSetField *field = NULL; @@ -1451,6 +1458,9 @@ UA_Server_computeWriterGroupOffsetTable(UA_Server *server, for(size_t i = 0; i < dsmCount; i++) { UA_DataSetMessage_clear(&dsmStore[i]); } + + UA_UNLOCK(&server->serviceMutex); + return res; }