Skip to content

Commit 0e85058

Browse files
authored
Merge pull request open62541#4010 from jpfr/merge_10_11
Merge 1.0 to 1.1.
2 parents bdf840f + 25c8c6a commit 0e85058

File tree

11 files changed

+329
-18
lines changed

11 files changed

+329
-18
lines changed

CMakeLists.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,13 @@ if(NOT UA_FORCE_CPP AND (CMAKE_COMPILER_IS_GNUCC OR "x${CMAKE_C_COMPILER_ID}" ST
588588
endif()
589589
endif()
590590

591-
if(UA_ENABLE_HARDENING AND ((CMAKE_BUILD_TYPE STREQUAL "Release") OR (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")))
592-
check_add_cc_flag("-D_FORTIFY_SOURCE=2") # run-time buffer overflow detection (needs at least -O1)
591+
if (NOT MINGW)
592+
if(UA_ENABLE_HARDENING AND ((CMAKE_BUILD_TYPE STREQUAL "Release") OR (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")))
593+
check_add_cc_flag("-D_FORTIFY_SOURCE=2") # run-time buffer overflow detection (needs at least -O1)
594+
endif()
593595
endif()
594596

597+
595598
# Strip release builds
596599
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel" OR CMAKE_BUILD_TYPE STREQUAL "Release")
597600
check_add_cc_flag("-ffunction-sections")

include/open62541/plugin/nodestore.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ typedef struct {
207207
UA_Byte accessLevel;
208208
UA_Double minimumSamplingInterval;
209209
UA_Boolean historizing;
210+
211+
/* Members specific to open62541 */
212+
UA_Boolean isDynamic; /* Some variables are "static" in the sense that they
213+
* are not attached to a dynamic process in the
214+
* background. Only dynamic variables conserve source
215+
* and server timestamp for the value attribute.
216+
* Static variables have timestamps of "now". */
210217
} UA_VariableNode;
211218

212219
/**

open62541.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Name: open62541
2-
Version: 0.3
2+
Version: 1.0.1
33
Release: 1%{?dist}
44
Summary: OPC UA implementation
55
License: MPLv2.0

src/server/ua_nodes.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ UA_VariableNode_copy(const UA_VariableNode *src, UA_VariableNode *dst) {
127127
dst->accessLevel = src->accessLevel;
128128
dst->minimumSamplingInterval = src->minimumSamplingInterval;
129129
dst->historizing = src->historizing;
130+
dst->isDynamic = src->isDynamic;
130131
return retval;
131132
}
132133

src/server/ua_server_internal.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ struct UA_Server {
129129

130130
/* DataChange Subscriptions */
131131
#ifdef UA_ENABLE_SUBSCRIPTIONS
132+
/* Last generated id (to generate server-wide unique ids) */
133+
UA_UInt32 lastSubscriptionId;
132134
/* Num active subscriptions */
133135
UA_UInt32 numSubscriptions;
134136
/* Num active monitored items */
@@ -140,7 +142,6 @@ struct UA_Server {
140142
#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS
141143
LIST_HEAD(conditionSourcelisthead, UA_ConditionSource) headConditionSource;
142144
#endif//UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS
143-
144145
#endif
145146

146147
/* Publish/Subscribe */

src/server/ua_services_attribute.c

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ readValueAttributeComplete(UA_Server *server, UA_Session *session,
180180
else
181181
retval = readValueAttributeFromDataSource(server, session, vn, v, timestamps, rangeptr);
182182

183+
/* Static Variables and VariableTypes have timestamps of "now". Will be set
184+
* below in the absence of predefined timestamps. */
185+
if(vn->nodeClass == UA_NODECLASS_VARIABLE) {
186+
if(!vn->isDynamic) {
187+
v->hasServerTimestamp = false;
188+
v->hasSourceTimestamp = false;
189+
}
190+
} else {
191+
v->hasServerTimestamp = false;
192+
v->hasSourceTimestamp = false;
193+
}
194+
183195
/* Clean up */
184196
if(rangeptr)
185197
UA_free(range.dimensions);
@@ -1191,20 +1203,19 @@ writeValueAttribute(UA_Server *server, UA_Session *session,
11911203
}
11921204
}
11931205

1194-
/* Ok, do it */
1195-
if(node->valueSource == UA_VALUESOURCE_DATA) {
1196-
/* Set the source timestamp if there is none */
1197-
UA_DateTime now = UA_DateTime_now();
1198-
if(!adjustedValue.hasSourceTimestamp) {
1199-
adjustedValue.sourceTimestamp = now;
1200-
adjustedValue.hasSourceTimestamp = true;
1201-
}
1206+
/* Set the source timestamp if there is none */
1207+
UA_DateTime now = UA_DateTime_now();
1208+
if(!adjustedValue.hasSourceTimestamp) {
1209+
adjustedValue.sourceTimestamp = now;
1210+
adjustedValue.hasSourceTimestamp = true;
1211+
}
12021212

1203-
if(!adjustedValue.hasServerTimestamp) {
1204-
adjustedValue.serverTimestamp = now;
1205-
adjustedValue.hasServerTimestamp = true;
1206-
}
1213+
/* Update the timestamp when the value was last updated in the server */
1214+
adjustedValue.serverTimestamp = now;
1215+
adjustedValue.hasServerTimestamp = true;
12071216

1217+
/* Ok, do it */
1218+
if(node->valueSource == UA_VALUESOURCE_DATA) {
12081219
if(!rangeptr)
12091220
retval = writeValueAttributeWithoutRange(node, &adjustedValue);
12101221
else

src/server/ua_services_nodemanagement.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,83 @@ recursiveDeleteNode(UA_Server *server, UA_Session *session,
12171217
UA_ExpandedNodeId *hierarchicalReferences,
12181218
const UA_Node *node, UA_Boolean removeTargetRefs);
12191219

1220+
static UA_StatusCode
1221+
setVariableNodeDynamic(UA_Server *server, UA_Session *session,
1222+
UA_Node *node, const void *_) {
1223+
(void)_; /* unused */
1224+
if(node->nodeClass == UA_NODECLASS_VARIABLE)
1225+
((UA_VariableNode*)node)->isDynamic = true;
1226+
return UA_STATUSCODE_GOOD;
1227+
}
1228+
1229+
static UA_StatusCode
1230+
checkSetIsDynamicVariable(UA_Server *server, UA_Session *session,
1231+
const UA_NodeId *nodeId) {
1232+
/* Get all hierarchical reference types */
1233+
UA_ExpandedNodeId *hierarchicalRefs = NULL;
1234+
size_t hierarchicalRefsSize = 0;
1235+
UA_NodeId hr = UA_NODEID_NUMERIC(0, UA_NS0ID_HIERARCHICALREFERENCES);
1236+
browseRecursive(server, 1, &hr, 1, &subtypeId, UA_BROWSEDIRECTION_FORWARD, true,
1237+
&hierarchicalRefsSize, &hierarchicalRefs);
1238+
1239+
UA_NodeId *hierarchicalRefs2 = (UA_NodeId*)
1240+
UA_Array_new(hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1241+
if(!hierarchicalRefs2) {
1242+
UA_Array_delete(hierarchicalRefs, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1243+
return UA_STATUSCODE_BADOUTOFMEMORY;
1244+
}
1245+
1246+
for(size_t i = 0; i < hierarchicalRefsSize; i++) {
1247+
hierarchicalRefs2[i] = hierarchicalRefs[i].nodeId;
1248+
UA_NodeId_init(&hierarchicalRefs[i].nodeId);
1249+
}
1250+
UA_Array_delete(hierarchicalRefs, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1251+
1252+
/* Is the variable under the server object? */
1253+
UA_NodeId serverNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
1254+
if(isNodeInTree(server, nodeId, &serverNodeId,
1255+
hierarchicalRefs2, hierarchicalRefsSize)) {
1256+
UA_Array_delete(hierarchicalRefs2, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1257+
return UA_STATUSCODE_GOOD;
1258+
}
1259+
1260+
/* Is the variable in the type hierarchy? */
1261+
UA_NodeId typesNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_TYPESFOLDER);
1262+
if(isNodeInTree(server, nodeId, &typesNodeId,
1263+
hierarchicalRefs2, hierarchicalRefsSize)) {
1264+
UA_Server_editNode(server, session, nodeId,
1265+
(UA_EditNodeCallback)setVariableNodeDynamic, NULL);
1266+
UA_Array_delete(hierarchicalRefs2, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1267+
return UA_STATUSCODE_GOOD;
1268+
}
1269+
1270+
/* Is the variable a property of a method node (InputArguments /
1271+
* OutputArguments)? */
1272+
UA_BrowseDescription bd;
1273+
UA_BrowseDescription_init(&bd);
1274+
bd.nodeId = *nodeId;
1275+
bd.browseDirection = UA_BROWSEDIRECTION_INVERSE;
1276+
bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY);
1277+
bd.includeSubtypes = false;
1278+
bd.nodeClassMask = UA_NODECLASS_METHOD;
1279+
UA_BrowseResult br;
1280+
UA_BrowseResult_init(&br);
1281+
UA_UInt32 maxrefs = 0;
1282+
Operation_Browse(server, session, &maxrefs, &bd, &br);
1283+
if(br.referencesSize > 0) {
1284+
UA_BrowseResult_clear(&br);
1285+
UA_Array_delete(hierarchicalRefs2, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1286+
return UA_STATUSCODE_GOOD;
1287+
}
1288+
1289+
/* Set the variable to "dynamic" */
1290+
UA_Server_editNode(server, session, nodeId,
1291+
(UA_EditNodeCallback)setVariableNodeDynamic, NULL);
1292+
1293+
UA_Array_delete(hierarchicalRefs2, hierarchicalRefsSize, &UA_TYPES[UA_TYPES_NODEID]);
1294+
return UA_STATUSCODE_GOOD;
1295+
}
1296+
12201297
/* Children, references, type-checking, constructors. */
12211298
UA_StatusCode
12221299
AddNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId) {
@@ -1250,6 +1327,14 @@ AddNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *nodeId)
12501327
goto cleanup;
12511328
}
12521329

1330+
/* Set variables to dynamic (source and server timestamps are meaningful) if
1331+
* they fulfill some conditions */
1332+
if(node->nodeClass == UA_NODECLASS_VARIABLE) {
1333+
retval = checkSetIsDynamicVariable(server, session, nodeId);
1334+
if(retval != UA_STATUSCODE_GOOD)
1335+
goto cleanup;
1336+
}
1337+
12531338
/* Call the constructor(s) */
12541339
constructor:
12551340
retval = recursiveCallConstructors(server, session, node, type);

src/server/ua_session.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ void UA_Session_updateLifetime(UA_Session *session) {
8989
#ifdef UA_ENABLE_SUBSCRIPTIONS
9090

9191
void UA_Session_addSubscription(UA_Server *server, UA_Session *session, UA_Subscription *newSubscription) {
92-
newSubscription->subscriptionId = ++session->lastSubscriptionId;
92+
newSubscription->subscriptionId = ++server->lastSubscriptionId;
9393

9494
LIST_INSERT_HEAD(&session->serverSubscriptions, newSubscription, listEntry);
9595
session->numSubscriptions++;

src/server/ua_session.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ typedef struct {
5050
UA_UInt16 availableContinuationPoints;
5151
ContinuationPoint *continuationPoints;
5252
#ifdef UA_ENABLE_SUBSCRIPTIONS
53-
UA_UInt32 lastSubscriptionId;
5453
UA_UInt32 lastSeenSubscriptionId;
5554
LIST_HEAD(UA_ListOfUASubscriptions, UA_Subscription) serverSubscriptions;
5655
SIMPLEQ_HEAD(UA_ListOfQueuedPublishResponses, UA_PublishResponseEntry) responseQueue;

tests/client/check_client_subscriptions.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,157 @@ START_TEST(Client_subscription_createDataChanges) {
434434
}
435435
END_TEST
436436

437+
/* An unchanged value shall not be published after a ModifyMonitoredItem */
438+
START_TEST(Client_subscription_modifyMonitoredItem) {
439+
UA_Client *client = UA_Client_new();
440+
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
441+
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
442+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
443+
444+
UA_Client_recv = client->connection.recv;
445+
client->connection.recv = UA_Client_recvTesting;
446+
447+
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
448+
UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
449+
NULL, NULL, NULL);
450+
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
451+
UA_UInt32 subId = response.subscriptionId;
452+
453+
UA_MonitoredItemCreateRequest items[1];
454+
UA_UInt32 newMonitoredItemIds[1];
455+
UA_Client_DataChangeNotificationCallback callbacks[1];
456+
UA_Client_DeleteMonitoredItemCallback deleteCallbacks[1];
457+
void *contexts[1];
458+
459+
/* Monitor the server state. Does not change during the unit test. */
460+
items[0] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
461+
items[0].requestedParameters.samplingInterval = publishingInterval * 0.2;
462+
callbacks[0] = dataChangeHandler;
463+
contexts[0] = NULL;
464+
deleteCallbacks[0] = NULL;
465+
466+
UA_CreateMonitoredItemsRequest createRequest;
467+
UA_CreateMonitoredItemsRequest_init(&createRequest);
468+
createRequest.subscriptionId = subId;
469+
createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_NEITHER;
470+
createRequest.itemsToCreate = items;
471+
createRequest.itemsToCreateSize = 1;
472+
UA_CreateMonitoredItemsResponse createResponse =
473+
UA_Client_MonitoredItems_createDataChanges(client, createRequest, contexts,
474+
callbacks, deleteCallbacks);
475+
476+
ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
477+
ck_assert_uint_eq(createResponse.resultsSize, 1);
478+
ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
479+
newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
480+
UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
481+
482+
UA_fakeSleep((UA_UInt32)publishingInterval + 1);
483+
484+
/* Receive the initial value */
485+
notificationReceived = false;
486+
countNotificationReceived = 0;
487+
retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
488+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
489+
ck_assert_uint_eq(notificationReceived, true);
490+
ck_assert_uint_eq(countNotificationReceived, 1);
491+
492+
/* No further update */
493+
notificationReceived = false;
494+
retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
495+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
496+
ck_assert_uint_eq(notificationReceived, false);
497+
ck_assert_uint_eq(countNotificationReceived, 1);
498+
499+
/* Modify the MonitoredItem and change the sampling interval */
500+
UA_MonitoredItemModifyRequest modify1;
501+
UA_MonitoredItemModifyRequest_init(&modify1);
502+
modify1.monitoredItemId = newMonitoredItemIds[0];
503+
modify1.requestedParameters.samplingInterval = publishingInterval * 1.5;
504+
505+
UA_ModifyMonitoredItemsRequest modifyRequest;
506+
UA_ModifyMonitoredItemsRequest_init(&modifyRequest);
507+
modifyRequest.subscriptionId = subId;
508+
modifyRequest.itemsToModify = &modify1;
509+
modifyRequest.itemsToModifySize = 1;
510+
511+
UA_ModifyMonitoredItemsResponse modifyResponse =
512+
UA_Client_MonitoredItems_modify(client, modifyRequest);
513+
ck_assert_uint_eq(modifyResponse.resultsSize, 1);
514+
ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
515+
UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyResponse);
516+
517+
/* Sleep longer than the publishing interval */
518+
UA_fakeSleep((UA_UInt32)publishingInterval + 1);
519+
520+
/* Don't receive an immediate update */
521+
notificationReceived = false;
522+
countNotificationReceived = 0;
523+
retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
524+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
525+
ck_assert_uint_eq(notificationReceived, false);
526+
ck_assert_uint_eq(countNotificationReceived, 0);
527+
528+
/* Modify the MonitoredItem and change the trigger. We want to see an
529+
* update. But not immediately. */
530+
UA_DataChangeFilter filter;
531+
UA_DataChangeFilter_init(&filter);
532+
filter.trigger = UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP;
533+
modify1.requestedParameters.filter.content.decoded.data = &filter;
534+
modify1.requestedParameters.filter.content.decoded.type =
535+
&UA_TYPES[UA_TYPES_DATACHANGEFILTER];
536+
modify1.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
537+
538+
modifyResponse = UA_Client_MonitoredItems_modify(client, modifyRequest);
539+
ck_assert_uint_eq(modifyResponse.resultsSize, 1);
540+
ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
541+
UA_ModifyMonitoredItemsResponse_deleteMembers(&modifyResponse);
542+
543+
/* Sleep longer than the publishing interval */
544+
UA_fakeSleep((UA_UInt32)publishingInterval + 1);
545+
UA_realSleep((UA_UInt32)publishingInterval + 1);
546+
547+
notificationReceived = false;
548+
countNotificationReceived = 0;
549+
retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
550+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
551+
ck_assert_uint_eq(notificationReceived, false);
552+
ck_assert_uint_eq(countNotificationReceived, 0);
553+
554+
/* Sleep long enough to trigger the next sampling.
555+
* This should now generate a value with the timestamp. */
556+
UA_fakeSleep((UA_UInt32)(publishingInterval * 0.6));
557+
UA_realSleep((UA_UInt32)(publishingInterval * 0.6));
558+
559+
/* Sleep long enough to trigger the publish callback */
560+
UA_fakeSleep((UA_UInt32)publishingInterval + 1);
561+
retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
562+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
563+
ck_assert_uint_eq(notificationReceived, true);
564+
ck_assert_uint_eq(countNotificationReceived, 1);
565+
566+
/* Delete and clean up */
567+
UA_DeleteMonitoredItemsRequest deleteRequest;
568+
UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
569+
deleteRequest.subscriptionId = subId;
570+
deleteRequest.monitoredItemIds = newMonitoredItemIds;
571+
deleteRequest.monitoredItemIdsSize = 1;
572+
573+
UA_DeleteMonitoredItemsResponse deleteResponse =
574+
UA_Client_MonitoredItems_delete(client, deleteRequest);
575+
576+
ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
577+
ck_assert_uint_eq(deleteResponse.resultsSize, 1);
578+
UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
579+
580+
retval = UA_Client_Subscriptions_deleteSingle(client, subId);
581+
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
582+
583+
UA_Client_disconnect(client);
584+
UA_Client_delete(client);
585+
}
586+
END_TEST
587+
437588
START_TEST(Client_subscription_createDataChanges_async) {
438589
UA_UInt32 reqId = 0;
439590
UA_Client *client = UA_Client_new();
@@ -1099,6 +1250,7 @@ static Suite* testSuite_Client(void) {
10991250
tcase_add_test(tc_client, Client_subscription_timeout);
11001251
tcase_add_test(tc_client, Client_subscription_connectionClose);
11011252
tcase_add_test(tc_client, Client_subscription_createDataChanges);
1253+
tcase_add_test(tc_client, Client_subscription_modifyMonitoredItem);
11021254
tcase_add_test(tc_client, Client_subscription_createDataChanges_async);
11031255
tcase_add_test(tc_client, Client_subscription_keepAlive);
11041256
tcase_add_test(tc_client, Client_subscription_without_notification);

0 commit comments

Comments
 (0)