/* 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/. */ #include #include #include "server/ua_server_internal.h" #include "server/ua_services.h" #include "server/ua_subscription.h" #include #include #include #include #include "ziptree.h" #include "test_helpers.h" #include "testing_clock.h" static UA_Server *server = NULL; static UA_Session *session = NULL; static UA_UInt32 monitored = 0; /* Number of active MonitoredItems */ static UA_Double defaultRequestedPublishingInterval = 100; /* in ms */ static UA_Subscription *deletingSubscription = NULL; static size_t monitoredItemsAfterDelete = 0; static UA_UInt32 subscriptionId; static UA_UInt32 monitoredItemId; static UA_Boolean subscriptionNotificationCalled = false; static UA_ApplicationNotificationType subscriptionNotificationType; static UA_UInt32 subscriptionNotificationId = 0; static UA_Boolean subscriptionNotificationEnabled = false; static size_t subscriptionNotificationMapSize = 0; static UA_Boolean closeAtMonitoredItemDeleted = false; static UA_StatusCode closeAtMonitoredItemDeletedResult; static UA_Boolean closeSessionAtMonitoredItemCreated; static UA_StatusCode closeSessionAtMonitoredItemCreatedResult; static UA_StatusCode expectedCreateMonitoredItemsServiceResult; typedef struct { UA_Boolean sessionLimit; UA_Boolean serverMonitoredItemsLimit; UA_Boolean subscriptionMonitoredItemsLimit; } LimitLogCapture; static void captureLimitLog(void *context, UA_LogLevel level, UA_LogCategory category, const char *msg, va_list args) { (void)args; LimitLogCapture *capture = (LimitLogCapture*)context; if(level != UA_LOGLEVEL_WARNING || category != UA_LOGCATEGORY_SESSION) return; if(strstr(msg, "Session resource limit")) capture->sessionLimit = true; if(strstr(msg, "server-wide MonitoredItem resource limit")) capture->serverMonitoredItemsLimit = true; if(strstr(msg, "per-Subscription MonitoredItem resource limit")) capture->subscriptionMonitoredItemsLimit = true; } static void monitoredItemNotificationCallback(UA_Server *server, UA_ApplicationNotificationType type, UA_KeyValueMap data) { if(type == UA_APPLICATIONNOTIFICATIONTYPE_MONITOREDITEM_CREATED) { monitored++; if(closeSessionAtMonitoredItemCreated) { closeSessionAtMonitoredItemCreated = false; const UA_Variant *sessionId = UA_KeyValueMap_get( &data, UA_QUALIFIEDNAME(0, "session-id")); ck_assert_ptr_ne(sessionId, NULL); ck_assert_ptr_ne(sessionId->data, NULL); closeSessionAtMonitoredItemCreatedResult = UA_Server_closeSession(server, (const UA_NodeId*)sessionId->data); } } if(type == UA_APPLICATIONNOTIFICATIONTYPE_MONITOREDITEM_DELETED) { monitored--; if(deletingSubscription) { ck_assert_ptr_eq(ZIP_ROOT(&deletingSubscription->monitoredItemsById), NULL); ck_assert_uint_eq(deletingSubscription->monitoredItemsSize, 0); ck_assert_uint_eq(server->monitoredItemsSize, monitoredItemsAfterDelete); /* Public server APIs can be called from notification callbacks. */ UA_QualifiedName browseName; UA_QualifiedName_init(&browseName); UA_StatusCode res = UA_Server_readBrowseName( server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), &browseName); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); UA_QualifiedName_clear(&browseName); } if(closeAtMonitoredItemDeleted) { closeAtMonitoredItemDeleted = false; closeAtMonitoredItemDeletedResult = UA_Server_closeSession(server, &session->sessionId); } } } static void subscriptionNotificationCallback(UA_Server *server, UA_ApplicationNotificationType type, UA_KeyValueMap data) { (void)server; if(type & UA_APPLICATIONNOTIFICATIONTYPE_SUBSCRIPTION) { subscriptionNotificationCalled = true; subscriptionNotificationType = type; subscriptionNotificationMapSize = data.mapSize; const UA_Variant *sid = UA_KeyValueMap_get(&data, UA_QUALIFIEDNAME(0,"subscription-id")); ck_assert_ptr_ne(sid, NULL); ck_assert_ptr_ne(sid->data, NULL); subscriptionNotificationId = *(const UA_UInt32*)sid->data; const UA_Variant *enabled = UA_KeyValueMap_get(&data, UA_QUALIFIEDNAME(0, "publishing-enabled")); ck_assert_ptr_ne(enabled, NULL); ck_assert_ptr_ne(enabled->data, NULL); subscriptionNotificationEnabled = *(const UA_Boolean*)enabled->data; } } static void createSession(void) { UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); request.requestedSessionTimeout = UA_UINT32_MAX; lockServer(server); UA_StatusCode retval = UA_Session_create(server, NULL, &request, &session); unlockServer(server); ck_assert_uint_eq(retval, 0); } static UA_Session * createSecondSession(void) { UA_Session *session2 = NULL; UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); request.requestedSessionTimeout = UA_UINT32_MAX; lockServer(server); UA_StatusCode retval = UA_Session_create(server, NULL, &request, &session2); unlockServer(server); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); ck_assert(session2 != NULL); return session2; } /* Create a session with authenticated user (for testing subscription transfer) */ static UA_Session * createAuthenticatedSession(const char *userId) { UA_Session *newSession = createSecondSession(); /* Set clientUserIdOfSession to simulate authenticated user */ lockServer(server); UA_String_clear(&newSession->clientUserIdOfSession); newSession->clientUserIdOfSession = UA_STRING_ALLOC(userId); unlockServer(server); return newSession; } static void setup(void) { deletingSubscription = NULL; monitoredItemsAfterDelete = 0; expectedCreateMonitoredItemsServiceResult = UA_STATUSCODE_GOOD; server = UA_Server_newForUnitTest(); ck_assert(server != NULL); UA_ServerConfig *config = UA_Server_getConfig(server); config->subscriptionNotificationCallback = subscriptionNotificationCallback; config->globalNotificationCallback = monitoredItemNotificationCallback; UA_Server_run_startup(server); createSession(); } static void teardown(void) { UA_Server_run_shutdown(server); UA_Server_delete(server); ck_assert_uint_eq(monitored, 0); /* All MonitoredItems have been de-registered */ } typedef struct { UA_Callback callback; void *application; void *context; size_t count; } ObservedCleanup; static void observeCleanup(void *application, void *context) { ObservedCleanup *observed = (ObservedCleanup*)application; observed->count++; observed->callback(observed->application, observed->context); } static void createSubscription(void) { UA_CreateSubscriptionRequest request; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); subscriptionId = response.subscriptionId; UA_CreateSubscriptionResponse_clear(&response); } static void createMonitoredItem(void) { UA_CreateMonitoredItemsRequest request; UA_CreateMonitoredItemsRequest_init(&request); request.subscriptionId = subscriptionId; request.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); rvi.attributeId = UA_ATTRIBUTEID_BROWSENAME; rvi.indexRange = UA_STRING_NULL; item.itemToMonitor = rvi; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); item.requestedParameters = params; request.itemsToCreateSize = 1; request.itemsToCreate = &item; UA_CreateMonitoredItemsResponse response; UA_CreateMonitoredItemsResponse_init(&response); lockServer(server); Service_CreateMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, expectedCreateMonitoredItemsServiceResult); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0].statusCode, UA_STATUSCODE_GOOD); monitoredItemId = response.results[0].monitoredItemId; ck_assert_uint_gt(monitoredItemId, 0); UA_MonitoredItemCreateRequest_clear(&item); UA_CreateMonitoredItemsResponse_clear(&response); } START_TEST(Server_createSubscription) { /* Create a subscription */ UA_CreateSubscriptionRequest request; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); subscriptionId = response.subscriptionId; UA_CreateSubscriptionResponse_clear(&response); } END_TEST START_TEST(Server_modifySubscription) { /* Create a subscription */ createSubscription(); /* Modify the subscription */ UA_ModifySubscriptionRequest request; UA_ModifySubscriptionRequest_init(&request); request.subscriptionId = subscriptionId; // just some arbitrary numbers to test. They have no specific reason request.requestedPublishingInterval = defaultRequestedPublishingInterval; request.requestedLifetimeCount = 1000; request.requestedMaxKeepAliveCount = 1000; request.maxNotificationsPerPublish = 1; request.priority = 10; UA_ModifySubscriptionResponse response; UA_ModifySubscriptionResponse_init(&response); lockServer(server); Service_ModifySubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_ModifySubscriptionResponse_clear(&response); } END_TEST START_TEST(Server_setPublishingMode) { createSubscription(); UA_SetPublishingModeRequest request; UA_SetPublishingModeRequest_init(&request); request.publishingEnabled = UA_TRUE; request.subscriptionIdsSize = 1; request.subscriptionIds = &subscriptionId; UA_SetPublishingModeResponse response; UA_SetPublishingModeResponse_init(&response); lockServer(server); Service_SetPublishingMode(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); UA_SetPublishingModeResponse_clear(&response); } END_TEST START_TEST(Server_republish) { createSubscription(); UA_RepublishRequest request; UA_RepublishRequest_init(&request); request.subscriptionId = subscriptionId; request.retransmitSequenceNumber = 0; UA_RepublishResponse response; UA_RepublishResponse_init(&response); lockServer(server); Service_Republish(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADMESSAGENOTAVAILABLE); UA_RepublishResponse_clear(&response); } END_TEST START_TEST(Server_republish_invalid) { UA_RepublishRequest request; UA_RepublishRequest_init(&request); request.subscriptionId = subscriptionId; request.retransmitSequenceNumber = 0; UA_RepublishResponse response; UA_RepublishResponse_init(&response); lockServer(server); Service_Republish(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_RepublishResponse_clear(&response); } END_TEST START_TEST(Server_deleteSubscription) { createSubscription(); /* Remove the subscription */ UA_DeleteSubscriptionsRequest del_request; UA_DeleteSubscriptionsRequest_init(&del_request); del_request.subscriptionIdsSize = 1; del_request.subscriptionIds = &subscriptionId; UA_DeleteSubscriptionsResponse del_response; UA_DeleteSubscriptionsResponse_init(&del_response); lockServer(server); Service_DeleteSubscriptions(server, session, &del_request, &del_response); unlockServer(server); ck_assert_uint_eq(del_response.resultsSize, 1); ck_assert_uint_eq(del_response.results[0], UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&del_response); } END_TEST START_TEST(Server_deleteSubscription_closeSessionFromMonitoredItemCallback) { createSubscription(); createMonitoredItem(); createMonitoredItem(); closeAtMonitoredItemDeleted = true; closeAtMonitoredItemDeletedResult = UA_STATUSCODE_BADINTERNALERROR; UA_DeleteSubscriptionsRequest request; UA_DeleteSubscriptionsRequest_init(&request); request.subscriptionIdsSize = 1; request.subscriptionIds = &subscriptionId; UA_DeleteSubscriptionsResponse response; UA_DeleteSubscriptionsResponse_init(&response); lockServer(server); Service_DeleteSubscriptions(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(closeAtMonitoredItemDeletedResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); UA_Server_run_iterate(server, false); ck_assert_uint_eq(server->subscriptionsSize, 0); UA_DeleteSubscriptionsResponse_clear(&response); } END_TEST START_TEST(Server_deleteSubscription_callbackSeesConsistentIndex) { createSubscription(); createMonitoredItem(); createMonitoredItem(); createMonitoredItem(); lockServer(server); deletingSubscription = UA_Session_getSubscriptionById(session, subscriptionId); ck_assert_ptr_ne(deletingSubscription, NULL); ck_assert_uint_eq(deletingSubscription->monitoredItemsSize, 3); ck_assert_uint_ge(server->monitoredItemsSize, 3); monitoredItemsAfterDelete = server->monitoredItemsSize - deletingSubscription->monitoredItemsSize; UA_DeleteSubscriptionsRequest request; UA_DeleteSubscriptionsRequest_init(&request); request.subscriptionIdsSize = 1; request.subscriptionIds = &subscriptionId; UA_DeleteSubscriptionsResponse response; UA_DeleteSubscriptionsResponse_init(&response); Service_DeleteSubscriptions(server, session, &request, &response); deletingSubscription = NULL; unlockServer(server); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); ck_assert_uint_eq(monitored, 0); UA_DeleteSubscriptionsResponse_clear(&response); } END_TEST START_TEST(Server_publishCallback) { /* Create a subscription */ UA_CreateSubscriptionRequest request; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subscriptionId1 = response.subscriptionId; UA_CreateSubscriptionResponse_clear(&response); /* Create a second subscription */ UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 subscriptionId2 = response.subscriptionId; UA_Double publishingInterval = response.revisedPublishingInterval; ck_assert(publishingInterval > 0.0f); UA_CreateSubscriptionResponse_clear(&response); /* Keepalive is set to max initially */ UA_Subscription *sub; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) ck_assert_uint_eq(sub->currentKeepAliveCount, sub->maxKeepAliveCount); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if ((sub->subscriptionId == subscriptionId1) || (sub->subscriptionId == subscriptionId2)) ck_assert_uint_eq(sub->currentKeepAliveCount, sub->maxKeepAliveCount+1); } /* Remove the subscriptions */ UA_DeleteSubscriptionsRequest del_request; UA_DeleteSubscriptionsRequest_init(&del_request); UA_UInt32 removeIds[2] = {subscriptionId1, subscriptionId2}; del_request.subscriptionIdsSize = 2; del_request.subscriptionIds = removeIds; UA_DeleteSubscriptionsResponse del_response; UA_DeleteSubscriptionsResponse_init(&del_response); lockServer(server); Service_DeleteSubscriptions(server, session, &del_request, &del_response); unlockServer(server); ck_assert_uint_eq(del_response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(del_response.resultsSize, 2); ck_assert_uint_eq(del_response.results[0], UA_STATUSCODE_GOOD); ck_assert_uint_eq(del_response.results[1], UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&del_response); } END_TEST START_TEST(Server_createMonitoredItems) { createSubscription(); createMonitoredItem(); } END_TEST START_TEST(Server_modifyMonitoredItems) { createSubscription(); createMonitoredItem(); UA_ModifyMonitoredItemsRequest request; UA_ModifyMonitoredItemsRequest_init(&request); request.subscriptionId = subscriptionId; request.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemModifyRequest item; UA_MonitoredItemModifyRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); rvi.attributeId = UA_ATTRIBUTEID_DESCRIPTION; rvi.indexRange = UA_STRING_NULL; item.monitoredItemId = monitoredItemId; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); item.requestedParameters = params; request.itemsToModifySize = 1; request.itemsToModify = &item; UA_ModifyMonitoredItemsResponse response; UA_ModifyMonitoredItemsResponse_init(&response); lockServer(server); Service_ModifyMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0].statusCode, UA_STATUSCODE_GOOD); UA_MonitoredItemModifyRequest_clear(&item); UA_ModifyMonitoredItemsResponse_clear(&response); } END_TEST START_TEST(Server_overflow) { /* Create a subscription */ UA_CreateSubscriptionRequest createSubscriptionRequest; UA_CreateSubscriptionResponse createSubscriptionResponse; UA_CreateSubscriptionRequest_init(&createSubscriptionRequest); createSubscriptionRequest.publishingEnabled = true; UA_CreateSubscriptionResponse_init(&createSubscriptionResponse); lockServer(server); Service_CreateSubscription(server, session, &createSubscriptionRequest, &createSubscriptionResponse); unlockServer(server); ck_assert_uint_eq(createSubscriptionResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 localSubscriptionId = createSubscriptionResponse.subscriptionId; UA_Double publishingInterval = createSubscriptionResponse.revisedPublishingInterval; ck_assert(publishingInterval > 0.0f); UA_CreateSubscriptionResponse_clear(&createSubscriptionResponse); /* Create a monitoredItem */ UA_CreateMonitoredItemsRequest createMonitoredItemsRequest; UA_CreateMonitoredItemsRequest_init(&createMonitoredItemsRequest); createMonitoredItemsRequest.subscriptionId = localSubscriptionId; createMonitoredItemsRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); rvi.attributeId = UA_ATTRIBUTEID_VALUE; rvi.indexRange = UA_STRING_NULL; item.itemToMonitor = rvi; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); item.requestedParameters = params; item.requestedParameters.queueSize = 3; item.requestedParameters.discardOldest = true; createMonitoredItemsRequest.itemsToCreateSize = 1; createMonitoredItemsRequest.itemsToCreate = &item; UA_CreateMonitoredItemsResponse createMonitoredItemsResponse; UA_CreateMonitoredItemsResponse_init(&createMonitoredItemsResponse); lockServer(server); Service_CreateMonitoredItems(server, session, &createMonitoredItemsRequest, &createMonitoredItemsResponse); unlockServer(server); ck_assert_uint_eq(createMonitoredItemsResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(createMonitoredItemsResponse.resultsSize, 1); ck_assert_uint_eq(createMonitoredItemsResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_UInt32 localMonitoredItemId = createMonitoredItemsResponse.results[0].monitoredItemId; ck_assert_uint_gt(localMonitoredItemId, 0); UA_MonitoredItemCreateRequest_clear(&item); UA_CreateMonitoredItemsResponse_clear(&createMonitoredItemsResponse); UA_MonitoredItem *mon = NULL; UA_Subscription *sub; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->subscriptionId == localSubscriptionId) mon = UA_Subscription_getMonitoredItem(sub, localMonitoredItemId); } ck_assert_ptr_ne(mon, NULL); UA_assert(mon); ck_assert_uint_eq(mon->queueSize, 1); ck_assert_uint_eq(mon->parameters.queueSize, 3); UA_Notification *notification; notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, false); UA_fakeSleep(1); /* modify the server's currenttime */ UA_LOCK(&server->serviceMutex); UA_MonitoredItem_sample(server, mon); UA_UNLOCK(&server->serviceMutex); ck_assert_uint_eq(mon->queueSize, 2); ck_assert_uint_eq(mon->parameters.queueSize, 3); notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, false); UA_fakeSleep(1); /* modify the server's currenttime */ UA_LOCK(&server->serviceMutex); UA_MonitoredItem_sample(server, mon); UA_UNLOCK(&server->serviceMutex); ck_assert_uint_eq(mon->queueSize, 3); ck_assert_uint_eq(mon->parameters.queueSize, 3); notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, false); UA_fakeSleep(1); /* modify the server's currenttime */ UA_LOCK(&server->serviceMutex); UA_MonitoredItem_sample(server, mon); UA_UNLOCK(&server->serviceMutex); ck_assert_uint_eq(mon->queueSize, 3); ck_assert_uint_eq(mon->parameters.queueSize, 3); notification = TAILQ_FIRST(&mon->queue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, true); ck_assert_uint_eq(notification->data.dataChange.value.status, UA_STATUSCODE_INFOTYPE_DATAVALUE | UA_STATUSCODE_INFOBITS_OVERFLOW); /* Remove status for next test */ notification->data.dataChange.value.hasStatus = false; notification->data.dataChange.value.status = 0; /* Modify the MonitoredItem */ UA_ModifyMonitoredItemsRequest modifyMonitoredItemsRequest; UA_ModifyMonitoredItemsRequest_init(&modifyMonitoredItemsRequest); modifyMonitoredItemsRequest.subscriptionId = localSubscriptionId; modifyMonitoredItemsRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemModifyRequest itemToModify; UA_MonitoredItemModifyRequest_init(&itemToModify); itemToModify.monitoredItemId = localMonitoredItemId; UA_MonitoringParameters_init(¶ms); itemToModify.requestedParameters = params; itemToModify.requestedParameters.queueSize = 2; itemToModify.requestedParameters.discardOldest = true; modifyMonitoredItemsRequest.itemsToModifySize = 1; modifyMonitoredItemsRequest.itemsToModify = &itemToModify; UA_ModifyMonitoredItemsResponse modifyMonitoredItemsResponse; UA_ModifyMonitoredItemsResponse_init(&modifyMonitoredItemsResponse); lockServer(server); Service_ModifyMonitoredItems(server, session, &modifyMonitoredItemsRequest, &modifyMonitoredItemsResponse); unlockServer(server); ck_assert_uint_eq(modifyMonitoredItemsResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(modifyMonitoredItemsResponse.resultsSize, 1); ck_assert_uint_eq(modifyMonitoredItemsResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_MonitoredItemModifyRequest_clear(&itemToModify); UA_ModifyMonitoredItemsResponse_clear(&modifyMonitoredItemsResponse); ck_assert_uint_eq(mon->queueSize, 2); ck_assert_uint_eq(mon->parameters.queueSize, 2); notification = TAILQ_FIRST(&mon->queue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, true); ck_assert_uint_eq(notification->data.dataChange.value.status, UA_STATUSCODE_INFOTYPE_DATAVALUE | UA_STATUSCODE_INFOBITS_OVERFLOW); /* Modify the MonitoredItem */ UA_ModifyMonitoredItemsRequest_init(&modifyMonitoredItemsRequest); modifyMonitoredItemsRequest.subscriptionId = localSubscriptionId; modifyMonitoredItemsRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemModifyRequest_init(&itemToModify); itemToModify.monitoredItemId = localMonitoredItemId; UA_MonitoringParameters_init(¶ms); itemToModify.requestedParameters = params; itemToModify.requestedParameters.queueSize = 1; modifyMonitoredItemsRequest.itemsToModifySize = 1; modifyMonitoredItemsRequest.itemsToModify = &itemToModify; UA_ModifyMonitoredItemsResponse_init(&modifyMonitoredItemsResponse); lockServer(server); Service_ModifyMonitoredItems(server, session, &modifyMonitoredItemsRequest, &modifyMonitoredItemsResponse); unlockServer(server); ck_assert_uint_eq(modifyMonitoredItemsResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(modifyMonitoredItemsResponse.resultsSize, 1); ck_assert_uint_eq(modifyMonitoredItemsResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_MonitoredItemModifyRequest_clear(&itemToModify); UA_ModifyMonitoredItemsResponse_clear(&modifyMonitoredItemsResponse); ck_assert_uint_eq(mon->queueSize, 1); ck_assert_uint_eq(mon->parameters.queueSize, 1); notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, false); /* Modify the MonitoredItem */ UA_ModifyMonitoredItemsRequest_init(&modifyMonitoredItemsRequest); modifyMonitoredItemsRequest.subscriptionId = localSubscriptionId; modifyMonitoredItemsRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemModifyRequest_init(&itemToModify); itemToModify.monitoredItemId = localMonitoredItemId; UA_MonitoringParameters_init(¶ms); itemToModify.requestedParameters = params; itemToModify.requestedParameters.discardOldest = false; itemToModify.requestedParameters.queueSize = 1; modifyMonitoredItemsRequest.itemsToModifySize = 1; modifyMonitoredItemsRequest.itemsToModify = &itemToModify; UA_ModifyMonitoredItemsResponse_init(&modifyMonitoredItemsResponse); lockServer(server); Service_ModifyMonitoredItems(server, session, &modifyMonitoredItemsRequest, &modifyMonitoredItemsResponse); unlockServer(server); ck_assert_uint_eq(modifyMonitoredItemsResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(modifyMonitoredItemsResponse.resultsSize, 1); ck_assert_uint_eq(modifyMonitoredItemsResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_MonitoredItemModifyRequest_clear(&itemToModify); UA_ModifyMonitoredItemsResponse_clear(&modifyMonitoredItemsResponse); UA_LOCK(&server->serviceMutex); UA_MonitoredItem_sample(server, mon); UA_UNLOCK(&server->serviceMutex); ck_assert_uint_eq(mon->queueSize, 1); ck_assert_uint_eq(mon->parameters.queueSize, 1); notification = TAILQ_FIRST(&mon->queue); ck_assert_uint_eq(notification->data.dataChange.value.hasStatus, false); /* the infobit is only set if the queue is larger than one */ /* Remove the subscriptions */ UA_DeleteSubscriptionsRequest deleteSubscriptionsRequest; UA_DeleteSubscriptionsRequest_init(&deleteSubscriptionsRequest); UA_UInt32 removeId = localSubscriptionId; deleteSubscriptionsRequest.subscriptionIdsSize = 1; deleteSubscriptionsRequest.subscriptionIds = &removeId; UA_DeleteSubscriptionsResponse deleteSubscriptionsResponse; UA_DeleteSubscriptionsResponse_init(&deleteSubscriptionsResponse); lockServer(server); Service_DeleteSubscriptions(server, session, &deleteSubscriptionsRequest, &deleteSubscriptionsResponse); unlockServer(server); ck_assert_uint_eq(deleteSubscriptionsResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(deleteSubscriptionsResponse.resultsSize, 1); ck_assert_uint_eq(deleteSubscriptionsResponse.results[0], UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&deleteSubscriptionsResponse); } END_TEST START_TEST(Server_setMonitoringMode) { createSubscription(); createMonitoredItem(); UA_SetMonitoringModeRequest request; UA_SetMonitoringModeRequest_init(&request); request.subscriptionId = subscriptionId; request.monitoringMode = UA_MONITORINGMODE_DISABLED; request.monitoredItemIdsSize = 1; request.monitoredItemIds = &monitoredItemId; UA_SetMonitoringModeResponse response; UA_SetMonitoringModeResponse_init(&response); lockServer(server); Service_SetMonitoringMode(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); UA_SetMonitoringModeResponse_clear(&response); } END_TEST START_TEST(Server_deleteMonitoredItems) { createSubscription(); createMonitoredItem(); UA_DeleteMonitoredItemsRequest request; UA_DeleteMonitoredItemsRequest_init(&request); request.subscriptionId = subscriptionId; request.monitoredItemIdsSize = 1; request.monitoredItemIds = &monitoredItemId; UA_DeleteMonitoredItemsResponse response; UA_DeleteMonitoredItemsResponse_init(&response); lockServer(server); Service_DeleteMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); UA_DeleteMonitoredItemsResponse_clear(&response); } END_TEST START_TEST(Server_lifeTimeCount) { /* Create a subscription */ UA_CreateSubscriptionRequest request; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; request.requestedLifetimeCount = 3; request.requestedMaxKeepAliveCount = 1; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.revisedMaxKeepAliveCount, 1); ck_assert_uint_eq(response.revisedLifetimeCount, 3); UA_CreateSubscriptionResponse_clear(&response); /* Create a second subscription */ UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; request.requestedLifetimeCount = 4; request.requestedMaxKeepAliveCount = 2; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.revisedMaxKeepAliveCount, 2); /* revisedLifetimeCount is revised to 3*MaxKeepAliveCount == 3 */ ck_assert_uint_eq(response.revisedLifetimeCount, 6); UA_Double publishingInterval = response.revisedPublishingInterval; ck_assert(publishingInterval > 0.0f); subscriptionId = response.subscriptionId; UA_CreateSubscriptionResponse_clear(&response); /* Add a MonitoredItem to the second subscription */ UA_CreateMonitoredItemsRequest mrequest; UA_CreateMonitoredItemsRequest_init(&mrequest); mrequest.subscriptionId = subscriptionId; mrequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); rvi.attributeId = UA_ATTRIBUTEID_BROWSENAME; rvi.indexRange = UA_STRING_NULL; item.itemToMonitor = rvi; item.requestedParameters.samplingInterval = publishingInterval / 5.0; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); item.requestedParameters = params; mrequest.itemsToCreateSize = 1; mrequest.itemsToCreate = &item; UA_CreateMonitoredItemsResponse mresponse; UA_CreateMonitoredItemsResponse_init(&mresponse); lockServer(server); Service_CreateMonitoredItems(server, session, &mrequest, &mresponse); unlockServer(server); ck_assert_uint_eq(mresponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(mresponse.resultsSize, 1); ck_assert_uint_eq(mresponse.results[0].statusCode, UA_STATUSCODE_GOOD); monitoredItemId = mresponse.results[0].monitoredItemId; ck_assert_uint_gt(monitoredItemId, 0); UA_MonitoredItemCreateRequest_clear(&item); UA_CreateMonitoredItemsResponse_clear(&mresponse); UA_Server_run_iterate(server, false); UA_UInt32 count = 0; UA_Subscription *sub; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { ck_assert_uint_eq(sub->currentLifetimeCount, 0); count++; } ck_assert_uint_eq(count, 2); UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { ck_assert_uint_eq(sub->currentLifetimeCount, 1); count++; } ck_assert_uint_eq(count, 2); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { ck_assert_uint_eq(sub->currentLifetimeCount, 2); count++; } ck_assert_uint_eq(count, 2); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { ck_assert_uint_eq(sub->currentLifetimeCount, 3); count++; } ck_assert_uint_eq(count, 2); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->statusChange == UA_STATUSCODE_GOOD) { ck_assert_uint_eq(sub->currentLifetimeCount, 4); count++; } } ck_assert_uint_eq(count, 1); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->statusChange == UA_STATUSCODE_GOOD) { ck_assert_uint_eq(sub->currentLifetimeCount, 5); count++; } } ck_assert_uint_eq(count, 1); /* Sleep until the publishing interval times out */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->statusChange == UA_STATUSCODE_GOOD) { ck_assert_uint_eq(sub->currentLifetimeCount, 6); count++; } } ck_assert_uint_eq(count, 1); /* Sleep until the publishing interval times out. The next iteration removes * the subscription. */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); count = 0; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->statusChange == UA_STATUSCODE_GOOD) count++; } ck_assert_uint_eq(count, 0); } END_TEST START_TEST(Server_invalidPublishingInterval) { UA_Double savedPublishingIntervalLimitsMin = server->config.publishingIntervalLimits.min; server->config.publishingIntervalLimits.min = 1; /* Create a subscription */ UA_CreateSubscriptionRequest request; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; request.requestedPublishingInterval = -5.0; // Must be positive UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert(response.revisedPublishingInterval == server->config.publishingIntervalLimits.min); UA_CreateSubscriptionResponse_clear(&response); server->config.publishingIntervalLimits.min = savedPublishingIntervalLimitsMin; } END_TEST START_TEST(Server_negativeSamplingInterval) { createSubscription(); UA_Double savedSamplingIntervalLimitsMin = server->config.samplingIntervalLimits.min; server->config.samplingIntervalLimits.min = 1; UA_CreateMonitoredItemsRequest request; UA_CreateMonitoredItemsRequest_init(&request); request.subscriptionId = subscriptionId; request.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); rvi.attributeId = UA_ATTRIBUTEID_BROWSENAME; rvi.indexRange = UA_STRING_NULL; item.itemToMonitor = rvi; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); params.samplingInterval = -5.0; item.requestedParameters = params; request.itemsToCreateSize = 1; request.itemsToCreate = &item; UA_CreateMonitoredItemsResponse response; UA_CreateMonitoredItemsResponse_init(&response); lockServer(server); Service_CreateMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0].statusCode, UA_STATUSCODE_GOOD); ck_assert(response.results[0].revisedSamplingInterval > 0.0); UA_MonitoredItemCreateRequest_clear(&item); UA_CreateMonitoredItemsResponse_clear(&response); server->config.samplingIntervalLimits.min = savedSamplingIntervalLimitsMin; } END_TEST START_TEST(Server_nanModifiedSamplingInterval) { createSubscription(); UA_CreateMonitoredItemsRequest createRequest; UA_CreateMonitoredItemsRequest_init(&createRequest); createRequest.subscriptionId = subscriptionId; createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest createItem; UA_MonitoredItemCreateRequest_init(&createItem); createItem.itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); createItem.itemToMonitor.attributeId = UA_ATTRIBUTEID_BROWSENAME; createItem.monitoringMode = UA_MONITORINGMODE_REPORTING; createItem.requestedParameters.samplingInterval = 100.0; createRequest.itemsToCreateSize = 1; createRequest.itemsToCreate = &createItem; UA_CreateMonitoredItemsResponse createResponse; UA_CreateMonitoredItemsResponse_init(&createResponse); lockServer(server); Service_CreateMonitoredItems(server, session, &createRequest, &createResponse); unlockServer(server); ck_assert_uint_eq(createResponse.resultsSize, 1); ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_UInt32 monitoredItemId = createResponse.results[0].monitoredItemId; UA_CreateMonitoredItemsResponse_clear(&createResponse); UA_MonitoredItemModifyRequest modifyItem; UA_MonitoredItemModifyRequest_init(&modifyItem); modifyItem.monitoredItemId = monitoredItemId; modifyItem.requestedParameters.samplingInterval = NAN; modifyItem.requestedParameters.queueSize = 1; UA_ModifyMonitoredItemsRequest modifyRequest; UA_ModifyMonitoredItemsRequest_init(&modifyRequest); modifyRequest.subscriptionId = subscriptionId; modifyRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; modifyRequest.itemsToModifySize = 1; modifyRequest.itemsToModify = &modifyItem; UA_ModifyMonitoredItemsResponse modifyResponse; UA_ModifyMonitoredItemsResponse_init(&modifyResponse); lockServer(server); Service_ModifyMonitoredItems(server, session, &modifyRequest, &modifyResponse); unlockServer(server); ck_assert_uint_eq(modifyResponse.resultsSize, 1); ck_assert_uint_eq(modifyResponse.results[0].statusCode, UA_STATUSCODE_GOOD); ck_assert(isfinite(modifyResponse.results[0].revisedSamplingInterval)); ck_assert(modifyResponse.results[0].revisedSamplingInterval == server->config.samplingIntervalLimits.min); UA_ModifyMonitoredItemsResponse_clear(&modifyResponse); } END_TEST START_TEST(Server_transferSubscriptionDiagnostics) { /* Test that subscription diagnostics counter is correctly maintained * when subscriptions are transferred between sessions */ /* Set authenticated user for first session to allow transfer */ lockServer(server); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("testuser"); unlockServer(server); /* Read initial subscription count */ UA_UInt32 initialCount = server->serverDiagnosticsSummary.currentSubscriptionCount; /* Create a subscription in the first session */ createSubscription(); createMonitoredItem(); /* Verify count increased by 1 */ UA_UInt32 countAfterCreate = server->serverDiagnosticsSummary.currentSubscriptionCount; ck_assert_uint_eq(countAfterCreate, initialCount + 1); /* Create a second session with same authenticated user */ UA_Session *session2 = createAuthenticatedSession("testuser"); /* Verify count is still the same (creating session doesn't change subscription count) */ UA_UInt32 countAfterSession2 = server->serverDiagnosticsSummary.currentSubscriptionCount; ck_assert_uint_eq(countAfterSession2, initialCount + 1); /* Transfer the subscription from session1 to session2 */ UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = false; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_TransferSubscriptionsResponse_clear(&transferResponse); /* Verify count is still the same after transfer */ UA_UInt32 countAfterTransfer = server->serverDiagnosticsSummary.currentSubscriptionCount; ck_assert_uint_eq(countAfterTransfer, initialCount + 1); /* Close the first session (subscription now belongs to session2) */ lockServer(server); UA_Server_closeSession(server, &session->sessionId); unlockServer(server); session = NULL; /* Verify count is still the same (subscription was transferred) */ UA_UInt32 countAfterSession1Close = server->serverDiagnosticsSummary.currentSubscriptionCount; ck_assert_uint_eq(countAfterSession1Close, initialCount + 1); /* Delete the subscription from session2 */ UA_DeleteSubscriptionsRequest delRequest; UA_DeleteSubscriptionsRequest_init(&delRequest); delRequest.subscriptionIdsSize = 1; delRequest.subscriptionIds = &subscriptionId; UA_DeleteSubscriptionsResponse delResponse; UA_DeleteSubscriptionsResponse_init(&delResponse); lockServer(server); Service_DeleteSubscriptions(server, session2, &delRequest, &delResponse); unlockServer(server); ck_assert_uint_eq(delResponse.resultsSize, 1); ck_assert_uint_eq(delResponse.results[0], UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&delResponse); /* Verify count decreased back to initial */ UA_UInt32 countAfterDelete = server->serverDiagnosticsSummary.currentSubscriptionCount; ck_assert_uint_eq(countAfterDelete, initialCount); /* Close session2 */ lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); /* Recreate session for other tests */ createSession(); } END_TEST #ifdef UA_ENABLE_DIAGNOSTICS static UA_StatusCode setBrowseName(UA_Server *server, UA_Session *adminSession, UA_Node *node, void *data) { UA_QualifiedName_clear(&node->head.browseName); return UA_QualifiedName_copy((UA_QualifiedName*)data, &node->head.browseName); } START_TEST(Server_diagnosticsRejectLongBrowseNames) { lockServer(server); UA_String_clear(&session->sessionName); session->sessionName = UA_STRING_ALLOC("diagnostics"); createSessionObject(server, session); UA_BrowsePath bp; UA_BrowsePath_init(&bp); bp.startingNode = session->sessionId; UA_RelativePathElement rpe; UA_RelativePathElement_init(&rpe); rpe.targetName = UA_QUALIFIEDNAME(0, "SessionDiagnostics"); bp.relativePath.elements = &rpe; bp.relativePath.elementsSize = 1; UA_BrowsePathResult bpr = translateBrowsePathToNodeIds(server, &bp); unlockServer(server); ck_assert_uint_eq(bpr.statusCode, UA_STATUSCODE_GOOD); ck_assert_uint_eq(bpr.targetsSize, 1); UA_NodeId sessionDiagnosticNode; UA_NodeId_init(&sessionDiagnosticNode); UA_StatusCode res = UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, &sessionDiagnosticNode); UA_BrowsePathResult_clear(&bpr); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); createSubscription(); lockServer(server); UA_Subscription *sub = getSubscriptionById(server, subscriptionId); ck_assert_ptr_ne(sub, NULL); UA_NodeId diagnosticNode; UA_NodeId_init(&diagnosticNode); res = UA_NodeId_copy(&sub->ns0Id, &diagnosticNode); unlockServer(server); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); char longNameData[128]; memset(longNameData, 'A', sizeof(longNameData)); UA_QualifiedName longName; UA_QualifiedName_init(&longName); longName.name.length = sizeof(longNameData); longName.name.data = (UA_Byte*)(uintptr_t)longNameData; lockServer(server); res = editNode(server, &server->adminSession, &diagnosticNode, UA_NODEATTRIBUTESMASK_BROWSENAME, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_BOTH, setBrowseName, &longName); res |= editNode(server, &server->adminSession, &sessionDiagnosticNode, UA_NODEATTRIBUTESMASK_BROWSENAME, UA_REFERENCETYPESET_NONE, UA_BROWSEDIRECTION_BOTH, setBrowseName, &longName); unlockServer(server); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); UA_Variant value; UA_Variant_init(&value); res = UA_Server_readValue(server, diagnosticNode, &value); ck_assert_uint_eq(res, UA_STATUSCODE_BADNOTIMPLEMENTED); UA_Variant_clear(&value); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = sessionDiagnosticNode; rvi.attributeId = UA_ATTRIBUTEID_VALUE; lockServer(server); UA_DataValue dv = readWithSession(server, session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER); unlockServer(server); ck_assert(dv.hasStatus); ck_assert_uint_eq(dv.status, UA_STATUSCODE_BADNOTIMPLEMENTED); UA_DataValue_clear(&dv); UA_NodeId_clear(&diagnosticNode); UA_NodeId_clear(&sessionDiagnosticNode); } END_TEST #endif /* Test anonymous user subscription transfer restriction */ START_TEST(Server_transferSubscription_anonymous) { /* Create subscription in first session (anonymous) */ createSubscription(); createMonitoredItem(); /* Create second anonymous session */ UA_Session *session2 = NULL; UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); request.requestedSessionTimeout = UA_UINT32_MAX; lockServer(server); UA_StatusCode retval = UA_Session_create(server, NULL, &request, &session2); unlockServer(server); ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); /* Attempt to transfer subscription from session to session2 */ UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = false; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); /* Verify transfer was rejected with Bad_UserAccessDenied */ ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_BADUSERACCESSDENIED); UA_TransferSubscriptionsResponse_clear(&transferResponse); /* Cleanup: close second session */ lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); } END_TEST /* --- Extended coverage tests --- */ START_TEST(Server_setTriggering_nothingToDo) { createSubscription(); createMonitoredItem(); UA_SetTriggeringRequest request; UA_SetTriggeringRequest_init(&request); request.subscriptionId = subscriptionId; request.triggeringItemId = monitoredItemId; request.linksToRemoveSize = 0; request.linksToAddSize = 0; UA_SetTriggeringResponse response; UA_SetTriggeringResponse_init(&response); lockServer(server); Service_SetTriggering(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADNOTHINGTODO); UA_SetTriggeringResponse_clear(&response); } END_TEST START_TEST(Server_setTriggering_invalidSubscription) { UA_SetTriggeringRequest request; UA_SetTriggeringRequest_init(&request); request.subscriptionId = 99999; /* invalid */ request.triggeringItemId = 1; UA_UInt32 linkToAdd = 2; request.linksToAddSize = 1; request.linksToAdd = &linkToAdd; UA_SetTriggeringResponse response; UA_SetTriggeringResponse_init(&response); lockServer(server); Service_SetTriggering(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_SetTriggeringResponse_clear(&response); } END_TEST START_TEST(Server_setTriggering_invalidMonitoredItem) { createSubscription(); UA_SetTriggeringRequest request; UA_SetTriggeringRequest_init(&request); request.subscriptionId = subscriptionId; request.triggeringItemId = 99999; /* invalid */ UA_UInt32 linkToAdd = 1; request.linksToAddSize = 1; request.linksToAdd = &linkToAdd; UA_SetTriggeringResponse response; UA_SetTriggeringResponse_init(&response); lockServer(server); Service_SetTriggering(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADMONITOREDITEMIDINVALID); UA_SetTriggeringResponse_clear(&response); } END_TEST START_TEST(Server_setTriggering_addAndRemoveLinks) { createSubscription(); createMonitoredItem(); /* Create a second monitored item to link */ UA_CreateMonitoredItemsRequest mreq; UA_CreateMonitoredItemsRequest_init(&mreq); mreq.subscriptionId = subscriptionId; mreq.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest mcreq; UA_MonitoredItemCreateRequest_init(&mcreq); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS); rvi.attributeId = UA_ATTRIBUTEID_VALUE; mcreq.itemToMonitor = rvi; mcreq.monitoringMode = UA_MONITORINGMODE_SAMPLING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); mcreq.requestedParameters = params; mreq.itemsToCreateSize = 1; mreq.itemsToCreate = &mcreq; UA_CreateMonitoredItemsResponse mres; UA_CreateMonitoredItemsResponse_init(&mres); lockServer(server); Service_CreateMonitoredItems(server, session, &mreq, &mres); unlockServer(server); ck_assert_uint_eq(mres.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 secondMonId = mres.results[0].monitoredItemId; UA_CreateMonitoredItemsResponse_clear(&mres); /* Add a triggering link: monitoredItemId triggers secondMonId */ UA_SetTriggeringRequest treq; UA_SetTriggeringRequest_init(&treq); treq.subscriptionId = subscriptionId; treq.triggeringItemId = monitoredItemId; treq.linksToAddSize = 1; treq.linksToAdd = &secondMonId; UA_SetTriggeringResponse tres; UA_SetTriggeringResponse_init(&tres); lockServer(server); Service_SetTriggering(server, session, &treq, &tres); unlockServer(server); ck_assert_uint_eq(tres.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(tres.addResultsSize, 1); ck_assert_uint_eq(tres.addResults[0], UA_STATUSCODE_GOOD); UA_SetTriggeringResponse_clear(&tres); /* Remove the link */ UA_SetTriggeringRequest treq2; UA_SetTriggeringRequest_init(&treq2); treq2.subscriptionId = subscriptionId; treq2.triggeringItemId = monitoredItemId; treq2.linksToRemoveSize = 1; treq2.linksToRemove = &secondMonId; UA_SetTriggeringResponse tres2; UA_SetTriggeringResponse_init(&tres2); lockServer(server); Service_SetTriggering(server, session, &treq2, &tres2); unlockServer(server); ck_assert_uint_eq(tres2.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(tres2.removeResultsSize, 1); ck_assert_uint_eq(tres2.removeResults[0], UA_STATUSCODE_GOOD); UA_SetTriggeringResponse_clear(&tres2); } END_TEST /* Links to add and to remove both count towards maxMonitoredItemsPerCall */ START_TEST(Server_setTriggering_maxMonitoredItemsPerCall) { createSubscription(); createMonitoredItem(); UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxMonitoredItemsPerCall; cfg->maxMonitoredItemsPerCall = 1; UA_UInt32 linkId = monitoredItemId; UA_SetTriggeringRequest req; UA_SetTriggeringRequest_init(&req); req.subscriptionId = subscriptionId; req.triggeringItemId = monitoredItemId; req.linksToAddSize = 1; req.linksToAdd = &linkId; req.linksToRemoveSize = 1; /* one link each, two operations in total */ req.linksToRemove = &linkId; UA_SetTriggeringResponse resp; UA_SetTriggeringResponse_init(&resp); lockServer(server); Service_SetTriggering(server, session, &req, &resp); unlockServer(server); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADTOOMANYOPERATIONS); ck_assert_uint_eq(resp.addResultsSize, 0); ck_assert_uint_eq(resp.removeResultsSize, 0); cfg->maxMonitoredItemsPerCall = orig; UA_SetTriggeringResponse_clear(&resp); } END_TEST static void addTriggeringLink(UA_UInt32 triggeringItemId, UA_UInt32 linkId) { UA_SetTriggeringRequest req; UA_SetTriggeringRequest_init(&req); req.subscriptionId = subscriptionId; req.triggeringItemId = triggeringItemId; req.linksToAddSize = 1; req.linksToAdd = &linkId; UA_SetTriggeringResponse resp; UA_SetTriggeringResponse_init(&resp); lockServer(server); Service_SetTriggering(server, session, &req, &resp); unlockServer(server); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(resp.addResultsSize, 1); ck_assert_uint_eq(resp.addResults[0], UA_STATUSCODE_GOOD); UA_SetTriggeringResponse_clear(&resp); } /* A link to a deleted MonitoredItem does not survive the next SetTriggering * on the triggering item, also if that item never reported in between */ START_TEST(Server_setTriggering_pruneDeletedLinks) { createSubscription(); createMonitoredItem(); UA_UInt32 triggeringId = monitoredItemId; createMonitoredItem(); UA_UInt32 deletedId = monitoredItemId; createMonitoredItem(); UA_UInt32 keptId = monitoredItemId; addTriggeringLink(triggeringId, deletedId); UA_DeleteMonitoredItemsRequest dreq; UA_DeleteMonitoredItemsRequest_init(&dreq); dreq.subscriptionId = subscriptionId; dreq.monitoredItemIdsSize = 1; dreq.monitoredItemIds = &deletedId; UA_DeleteMonitoredItemsResponse dres; UA_DeleteMonitoredItemsResponse_init(&dres); lockServer(server); Service_DeleteMonitoredItems(server, session, &dreq, &dres); unlockServer(server); ck_assert_uint_eq(dres.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_DeleteMonitoredItemsResponse_clear(&dres); addTriggeringLink(triggeringId, keptId); /* Only the link to the existing MonitoredItem is left */ size_t linksSize = 0; UA_UInt32 firstLink = 0; lockServer(server); UA_Subscription *sub = UA_Session_getSubscriptionById(session, subscriptionId); UA_MonitoredItem *mon = (sub) ? UA_Subscription_getMonitoredItem(sub, triggeringId) : NULL; if(mon) { linksSize = mon->triggeringLinksSize; if(linksSize > 0) firstLink = mon->triggeringLinks[0]; } unlockServer(server); ck_assert(mon != NULL); ck_assert_uint_eq(linksSize, 1); ck_assert_uint_eq(firstLink, keptId); } END_TEST START_TEST(Server_modifySubscription_invalid) { /* Modify a subscription that doesn't exist */ UA_ModifySubscriptionRequest request; UA_ModifySubscriptionRequest_init(&request); request.subscriptionId = 99999; UA_ModifySubscriptionResponse response; UA_ModifySubscriptionResponse_init(&response); lockServer(server); Service_ModifySubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_ModifySubscriptionResponse_clear(&response); } END_TEST START_TEST(Server_deleteSubscription_invalid) { /* Delete a subscription that doesn't exist */ UA_DeleteSubscriptionsRequest request; UA_DeleteSubscriptionsRequest_init(&request); UA_UInt32 invalidSubId = 99999; request.subscriptionIdsSize = 1; request.subscriptionIds = &invalidSubId; UA_DeleteSubscriptionsResponse response; UA_DeleteSubscriptionsResponse_init(&response); lockServer(server); Service_DeleteSubscriptions(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_DeleteSubscriptionsResponse_clear(&response); } END_TEST START_TEST(Server_createMonitoredItems_invalidSubscription) { UA_CreateMonitoredItemsRequest request; UA_CreateMonitoredItemsRequest_init(&request); request.subscriptionId = 99999; /* invalid */ request.timestampsToReturn = UA_TIMESTAMPSTORETURN_SERVER; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER); rvi.attributeId = UA_ATTRIBUTEID_BROWSENAME; item.itemToMonitor = rvi; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); item.requestedParameters = params; request.itemsToCreateSize = 1; request.itemsToCreate = &item; UA_CreateMonitoredItemsResponse response; UA_CreateMonitoredItemsResponse_init(&response); lockServer(server); Service_CreateMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_CreateMonitoredItemsResponse_clear(&response); } END_TEST START_TEST(Server_setMonitoringMode_invalid) { createSubscription(); createMonitoredItem(); UA_SetMonitoringModeRequest request; UA_SetMonitoringModeRequest_init(&request); request.subscriptionId = subscriptionId; request.monitoringMode = UA_MONITORINGMODE_DISABLED; UA_UInt32 invalidMon = 99999; request.monitoredItemIdsSize = 1; request.monitoredItemIds = &invalidMon; UA_SetMonitoringModeResponse response; UA_SetMonitoringModeResponse_init(&response); lockServer(server); Service_SetMonitoringMode(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_BADMONITOREDITEMIDINVALID); UA_SetMonitoringModeResponse_clear(&response); } END_TEST START_TEST(Server_modifyMonitoredItems_invalidSubscription) { UA_ModifyMonitoredItemsRequest request; UA_ModifyMonitoredItemsRequest_init(&request); request.subscriptionId = 99999; UA_MonitoredItemModifyRequest mmod; UA_MonitoredItemModifyRequest_init(&mmod); mmod.monitoredItemId = 1; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); mmod.requestedParameters = params; request.itemsToModifySize = 1; request.itemsToModify = &mmod; UA_ModifyMonitoredItemsResponse response; UA_ModifyMonitoredItemsResponse_init(&response); lockServer(server); Service_ModifyMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_ModifyMonitoredItemsResponse_clear(&response); } END_TEST START_TEST(Server_deleteMonitoredItems_invalidSubscription) { UA_DeleteMonitoredItemsRequest request; UA_DeleteMonitoredItemsRequest_init(&request); request.subscriptionId = 99999; UA_UInt32 monId = 1; request.monitoredItemIdsSize = 1; request.monitoredItemIds = &monId; UA_DeleteMonitoredItemsResponse response; UA_DeleteMonitoredItemsResponse_init(&response); lockServer(server); Service_DeleteMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID); UA_DeleteMonitoredItemsResponse_clear(&response); } END_TEST START_TEST(Server_transferSubscription_sendInitialValues) { createSubscription(); createMonitoredItem(); /* Create a second authenticated session */ UA_Session *session2 = createAuthenticatedSession("user1"); /* Also authenticate the first session */ lockServer(server); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("user1"); unlockServer(server); /* Transfer with sendInitialValues = true */ UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = true; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); } END_TEST START_TEST(Server_createSubscription_notificationCallback) { UA_ServerConfig *config = UA_Server_getConfig(server); subscriptionNotificationCalled = false; subscriptionNotificationType = (UA_ApplicationNotificationType)0; subscriptionNotificationId = 0; subscriptionNotificationEnabled = false; subscriptionNotificationMapSize = 0; UA_CreateSubscriptionRequest request; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert(subscriptionNotificationCalled == true); ck_assert_uint_gt(subscriptionNotificationMapSize, 0); ck_assert_uint_eq(subscriptionNotificationId, response.subscriptionId); ck_assert(subscriptionNotificationEnabled == true); config->subscriptionNotificationCallback = NULL; UA_CreateSubscriptionResponse_clear(&response); } END_TEST START_TEST(Server_closeSessionFromMonitoredItemCreated) { createSubscription(); UA_Subscription *closedSubscription = getSubscriptionById(server, subscriptionId); ck_assert_ptr_ne(closedSubscription, NULL); UA_Session *closedSession = session; closeSessionAtMonitoredItemCreated = true; closeSessionAtMonitoredItemCreatedResult = UA_STATUSCODE_BADINTERNALERROR; expectedCreateMonitoredItemsServiceResult = UA_STATUSCODE_BADSESSIONCLOSED; createMonitoredItem(); ck_assert_uint_eq(closeSessionAtMonitoredItemCreatedResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(server->sessionCount, 0); ck_assert_uint_eq(server->subscriptionsSize, 0); ck_assert_uint_eq(server->monitoredItemsSize, 0); /* Drain the MonitoredItem, Subscription and Session cleanup callbacks. * The create path must not enqueue the MonitoredItem callback again and * overwrite its link to the later cleanup nodes. */ ObservedCleanup subscriptionCleanup = { closedSubscription->delayedFreePointers.callback, closedSubscription->delayedFreePointers.application, closedSubscription->delayedFreePointers.context, 0 }; ck_assert(subscriptionCleanup.callback != NULL); closedSubscription->delayedFreePointers.callback = observeCleanup; closedSubscription->delayedFreePointers.application = &subscriptionCleanup; session_list_entry *closedSessionEntry = container_of(closedSession, session_list_entry, session); ObservedCleanup sessionCleanup = { closedSessionEntry->cleanupCallback.callback, closedSessionEntry->cleanupCallback.application, closedSessionEntry->cleanupCallback.context, 0 }; ck_assert(sessionCleanup.callback != NULL); closedSessionEntry->cleanupCallback.callback = observeCleanup; closedSessionEntry->cleanupCallback.application = &sessionCleanup; UA_Server_run_iterate(server, false); UA_Server_run_iterate(server, false); ck_assert_uint_eq(subscriptionCleanup.count, 1); ck_assert_uint_eq(sessionCleanup.count, 1); session = NULL; } END_TEST /* Override hook: allow transfer of a detached subscription only when the new * session authenticates as the expected user. */ static const char *recoverOverrideExpectedUser = NULL; static UA_Boolean allowTransferSubscription_recoverOverride(UA_Server *server, UA_AccessControl *ac, const UA_NodeId *oldSessionId, void *oldSessionContext, const UA_NodeId *newSessionId, void *newSessionContext) { /* Detached subscription: oldSessionId is null/zero after session timeout. */ if(oldSessionId && oldSessionId->identifierType != UA_NODEIDTYPE_NUMERIC) return false; UA_Variant newUserId; UA_Variant_init(&newUserId); UA_Server_getSessionAttribute(server, newSessionId, UA_QUALIFIEDNAME(0, "clientUserId"), &newUserId); UA_Boolean result = false; if(newUserId.type == &UA_TYPES[UA_TYPES_STRING] && recoverOverrideExpectedUser != NULL) { UA_String *uid = (UA_String*)newUserId.data; UA_String expected = UA_STRING((char*)(uintptr_t)recoverOverrideExpectedUser); if(UA_String_equal(uid, &expected)) result = true; } UA_Variant_clear(&newUserId); return result; } /* Transfer between two live Sessions without a queued PublishRequest on the * old Session. The original subscription waits to send the * Good_SubscriptionTransferred StatusChange. It is late and first in the queue * of the old Session, so that the next PublishRequest of the old Session * receives the StatusChange (and not a keep-alive of another subscription). */ START_TEST(Server_transferSubscription_statusChangeWithNextPublish) { lockServer(server); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("testuser"); unlockServer(server); createSubscription(); /* Stays in the old session */ createSubscription(); /* Transferred */ UA_UInt32 transferredId = subscriptionId; UA_Session *session2 = createAuthenticatedSession("testuser"); UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &transferredId; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); UA_Subscription *first = TAILQ_FIRST(&session->subscriptions); ck_assert_ptr_ne(first, NULL); ck_assert_uint_eq(first->subscriptionId, transferredId); ck_assert_uint_eq(first->statusChange, UA_STATUSCODE_GOODSUBSCRIPTIONTRANSFERRED); ck_assert(first->late); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); } END_TEST static void dummyRepeatedCallback(UA_Server *s, void *data) {} /* The first publishing cycle ends one publishing interval after the * subscription was created (Part 4, 5.13.1.1). The publish timer must not be * batched with another timer of the same interval, which would end the first * cycle (and send the first keep-alive) early. */ START_TEST(Server_firstPublishingCycleNotBatched) { UA_UInt64 otherTimer; ck_assert_uint_eq(UA_Server_addRepeatedCallback(server, dummyRepeatedCallback, NULL, 1000.0, &otherTimer), UA_STATUSCODE_GOOD); UA_fakeSleep(200); UA_Server_run_iterate(server, false); UA_CreateSubscriptionRequest request; UA_CreateSubscriptionRequest_init(&request); request.publishingEnabled = true; request.requestedPublishingInterval = 1000.0; UA_CreateSubscriptionResponse response; UA_CreateSubscriptionResponse_init(&response); lockServer(server); Service_CreateSubscription(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert(response.revisedPublishingInterval == 1000.0); subscriptionId = response.subscriptionId; UA_CreateSubscriptionResponse_clear(&response); lockServer(server); UA_Subscription *sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_uint_eq(sub->currentKeepAliveCount, sub->maxKeepAliveCount); /* The other timer fires after 800ms. The first publishing cycle has not * ended yet. */ UA_fakeSleep(801); UA_Server_run_iterate(server, false); ck_assert_uint_eq(sub->currentKeepAliveCount, sub->maxKeepAliveCount); /* The first publishing cycle ends after 1000ms */ UA_fakeSleep(200); UA_Server_run_iterate(server, false); ck_assert_uint_eq(sub->currentKeepAliveCount, sub->maxKeepAliveCount + 1); UA_Server_removeCallback(server, otherTimer); } END_TEST /* A subscription survives the session timeout (detached). Only a session of * the same user can transfer it. */ START_TEST(Server_subscriptionSurvivesSessionTimeoutTransferableToSameUser) { /* Authenticated user to allow transfer */ lockServer(server); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("testuser"); unlockServer(server); createSubscription(); createMonitoredItem(); /* Subscription exists */ lockServer(server); UA_Subscription *sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); /* Force session timeout */ lockServer(server); session->validTill = UA_DateTime_nowMonotonic() - UA_DATETIME_SEC; cleanupSessions(server, UA_DateTime_nowMonotonic()); unlockServer(server); session = NULL; /* Subscription survives in server-wide list, detached */ lockServer(server); sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, NULL); UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = false; /* A different user cannot take over the detached subscription */ UA_Session *session3 = createAuthenticatedSession("otheruser"); UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session3, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_BADUSERACCESSDENIED); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, NULL); /* The same user can recover the detached subscription */ UA_Session *session2 = createAuthenticatedSession("testuser"); UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, session2); lockServer(server); UA_Server_closeSession(server, &session3->sessionId); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); createSession(); }END_TEST /* An anonymous detached subscription cannot be transferred if the * SecureChannels are not secure: the ApplicationUri of the client is not * verified against a certificate (same rule as for attached subscriptions). */ START_TEST(Server_detachedSubscription_anonymousInsecureNotTransferable) { createSubscription(); createMonitoredItem(); /* Close the (anonymous) session without deleting the subscription */ lockServer(server); UA_Subscription *sub = getSubscriptionById(server, subscriptionId); ck_assert_ptr_ne(sub, NULL); UA_Session_detachSubscription(server, session, sub, true); UA_Server_closeSession(server, &session->sessionId); unlockServer(server); session = NULL; lockServer(server); sub = getSubscriptionById(server, subscriptionId); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, NULL); unlockServer(server); UA_Session *session2 = createSecondSession(); UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_BADUSERACCESSDENIED); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); createSession(); }END_TEST /* Companion to the previous test: a custom allowTransferSubscription hook is * consulted (with oldSessionId NULL) for a detached subscription after the * server has verified that the new session has the same user. */ START_TEST(Server_subscriptionRecoverableWithOverride) { /* Install override; restore the previous hook at the end. */ UA_AccessControl *ac = &server->config.accessControl; UA_Boolean (*prevHook)(UA_Server *, UA_AccessControl *, const UA_NodeId *, void *, const UA_NodeId *, void *) = ac->allowTransferSubscription; ac->allowTransferSubscription = allowTransferSubscription_recoverOverride; recoverOverrideExpectedUser = "testuser"; lockServer(server); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("testuser"); unlockServer(server); createSubscription(); createMonitoredItem(); lockServer(server); UA_Subscription *sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); /* Force session timeout */ lockServer(server); session->validTill = UA_DateTime_nowMonotonic() - UA_DATETIME_SEC; cleanupSessions(server, UA_DateTime_nowMonotonic()); unlockServer(server); session = NULL; /* Subscription detached */ lockServer(server); sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, NULL); /* Override allows the same-user session to reclaim the detached subscription. */ UA_Session *session2 = createAuthenticatedSession("testuser"); UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = false; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); unlockServer(server); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_TransferSubscriptionsResponse_clear(&transferResponse); /* Re-attached to the new session */ lockServer(server); sub = getSubscriptionById(server, subscriptionId); unlockServer(server); ck_assert_ptr_ne(sub, NULL); ck_assert_ptr_eq(sub->session, session2); /* Restore hook before cleanup so other tests see the default policy. */ ac->allowTransferSubscription = prevHook; recoverOverrideExpectedUser = NULL; /* Cleanup */ UA_DeleteSubscriptionsRequest delRequest; UA_DeleteSubscriptionsRequest_init(&delRequest); delRequest.subscriptionIdsSize = 1; delRequest.subscriptionIds = &subscriptionId; UA_DeleteSubscriptionsResponse delResponse; UA_DeleteSubscriptionsResponse_init(&delResponse); lockServer(server); Service_DeleteSubscriptions(server, session2, &delRequest, &delResponse); unlockServer(server); ck_assert_uint_eq(delResponse.resultsSize, 1); ck_assert_uint_eq(delResponse.results[0], UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&delResponse); lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); createSession(); }END_TEST /* DataSource nodes with SamplingInterval=0 must use PUBLISH (periodic) sampling, * not EVENT (backpointer) sampling, because they are never written via the Write * service; tested with Server/ServerStatus/CurrentTime (i=2258). */ START_TEST(Server_dataSourceSamplingIntervalZero) { /* Create a subscription */ UA_CreateSubscriptionRequest subRequest; UA_CreateSubscriptionRequest_init(&subRequest); subRequest.publishingEnabled = UA_TRUE; subRequest.requestedPublishingInterval = defaultRequestedPublishingInterval; UA_CreateSubscriptionResponse subResponse; UA_CreateSubscriptionResponse_init(&subResponse); lockServer(server); Service_CreateSubscription(server, session, &subRequest, &subResponse); unlockServer(server); ck_assert_uint_eq(subResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_UInt32 localSubId = subResponse.subscriptionId; UA_Double publishingInterval = subResponse.revisedPublishingInterval; UA_CreateSubscriptionResponse_clear(&subResponse); /* Create a MonitoredItem on Server/ServerStatus/CurrentTime (i=2258) * with SamplingInterval=0 */ UA_CreateMonitoredItemsRequest mrequest; UA_CreateMonitoredItemsRequest_init(&mrequest); mrequest.subscriptionId = localSubId; mrequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); UA_ReadValueId rvi; UA_ReadValueId_init(&rvi); rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); rvi.attributeId = UA_ATTRIBUTEID_VALUE; item.itemToMonitor = rvi; item.monitoringMode = UA_MONITORINGMODE_REPORTING; UA_MonitoringParameters params; UA_MonitoringParameters_init(¶ms); params.samplingInterval = 0.0; /* key: SamplingInterval=0 on a DataSource node */ params.queueSize = 10; params.discardOldest = UA_TRUE; item.requestedParameters = params; mrequest.itemsToCreateSize = 1; mrequest.itemsToCreate = &item; UA_CreateMonitoredItemsResponse mresponse; UA_CreateMonitoredItemsResponse_init(&mresponse); lockServer(server); Service_CreateMonitoredItems(server, session, &mrequest, &mresponse); unlockServer(server); ck_assert_uint_eq(mresponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(mresponse.resultsSize, 1); ck_assert_uint_eq(mresponse.results[0].statusCode, UA_STATUSCODE_GOOD); UA_UInt32 localMonId = mresponse.results[0].monitoredItemId; ck_assert_uint_gt(localMonId, 0); /* The server may revise the sampling interval (e.g. to the publishing * interval). Accept any non-negative value; the important structural * assertion is that the item uses PUBLISH sampling, not EVENT sampling. */ ck_assert(mresponse.results[0].revisedSamplingInterval >= 0.0); UA_MonitoredItemCreateRequest_clear(&item); UA_CreateMonitoredItemsResponse_clear(&mresponse); /* Locate the MonitoredItem and verify it is registered with PUBLISH sampling, * not EVENT (backpointer) sampling. This is the structural assertion for the * fix: a DataSource node with SamplingInterval=0 must land in * sub->samplingMonitoredItems, not in the node's monitoredItems backpointer * list. */ UA_Subscription *sub = NULL; TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { if(sub->subscriptionId == localSubId) break; } ck_assert_ptr_ne(sub, NULL); UA_MonitoredItem *mon = NULL; UA_MonitoredItem *tmpMon; LIST_FOREACH(tmpMon, &sub->samplingMonitoredItems, sampling.subscriptionSampling) { if(tmpMon->monitoredItemId == localMonId) { mon = tmpMon; break; } } ck_assert_ptr_ne(mon, NULL); ck_assert_uint_eq(mon->samplingType, UA_MONITOREDITEMSAMPLINGTYPE_PUBLISH); /* First publish cycle: capture the initial CurrentTime value */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); ck_assert_uint_ge(mon->queueSize, 1); UA_Notification *notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_ptr_ne(notification, NULL); ck_assert(notification->data.dataChange.value.hasValue); ck_assert(notification->data.dataChange.value.value.type == &UA_TYPES[UA_TYPES_DATETIME]); UA_DateTime t1 = *(UA_DateTime*)notification->data.dataChange.value.value.data; UA_UInt32 queueBefore = mon->queueSize; /* Advance the test clock and trigger a second publish cycle. * readCurrentTime() returns UA_DateTime_now(), which is controlled by * UA_fakeSleep, so the value in the second cycle will differ from t1 and * a new DataChangeNotification must be enqueued. */ UA_fakeSleep((UA_UInt32)publishingInterval + 1); UA_Server_run_iterate(server, false); ck_assert_uint_gt(mon->queueSize, queueBefore); notification = TAILQ_LAST(&mon->queue, NotificationQueue); ck_assert_ptr_ne(notification, NULL); ck_assert(notification->data.dataChange.value.hasValue); UA_DateTime t2 = *(UA_DateTime*)notification->data.dataChange.value.value.data; ck_assert(t2 > t1); /* Cleanup */ UA_DeleteSubscriptionsRequest delRequest; UA_DeleteSubscriptionsRequest_init(&delRequest); delRequest.subscriptionIdsSize = 1; delRequest.subscriptionIds = &localSubId; UA_DeleteSubscriptionsResponse delResponse; UA_DeleteSubscriptionsResponse_init(&delResponse); lockServer(server); Service_DeleteSubscriptions(server, session, &delRequest, &delResponse); unlockServer(server); ck_assert_uint_eq(delResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); UA_DeleteSubscriptionsResponse_clear(&delResponse); } END_TEST /* ==== Subscription / MonitoredItem limit guards (white-box) ==== */ START_TEST(Server_republish_unknownSequenceNumber) { /* src/server/ua_services_subscription.c:491-494: * if(!entry) { * response->responseHeader.serviceResult = UA_STATUSCODE_BADMESSAGENOTAVAILABLE; * return true; * } * The existing Server_republish_invalid uses subscriptionId = 0 * (BADSUBSCRIPTIONIDINVALID branch at 472-475). The * BADMESSAGENOTAVAILABLE branch needs a valid subscriptionId + * a sequence number that is not in the retransmission queue. */ createSubscription(); UA_RepublishRequest request; UA_RepublishRequest_init(&request); request.subscriptionId = subscriptionId; request.retransmitSequenceNumber = 99999; /* not in queue */ UA_RepublishResponse response; UA_RepublishResponse_init(&response); lockServer(server); Service_Republish(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_BADMESSAGENOTAVAILABLE); UA_RepublishResponse_clear(&response); } END_TEST START_TEST(Server_createSubscription_maxSubscriptionsPerSession) { /* src/server/ua_services_subscription.c:109-115: * if(maxSubscriptionsPerSession != 0 && * session->subscriptionsSize >= maxSubscriptionsPerSession) { * response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYSUBSCRIPTIONS; * return true; * } */ createSubscription(); /* first subscription */ UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxSubscriptionsPerSession; cfg->maxSubscriptionsPerSession = 1; /* already at the limit */ UA_CreateSubscriptionRequest req; UA_CreateSubscriptionRequest_init(&req); req.requestedPublishingInterval = 500.0; req.requestedLifetimeCount = 100; req.requestedMaxKeepAliveCount = 10; UA_CreateSubscriptionResponse resp; UA_CreateSubscriptionResponse_init(&resp); lockServer(server); UA_Boolean ok = Service_CreateSubscription(server, session, &req, &resp); unlockServer(server); ck_assert(ok); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADTOOMANYSUBSCRIPTIONS); ck_assert_uint_eq(resp.subscriptionId, 0); cfg->maxSubscriptionsPerSession = orig; UA_CreateSubscriptionResponse_clear(&resp); } END_TEST START_TEST(Server_createSession_unlimited) { UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt16 orig = cfg->maxSessions; cfg->maxSessions = 0; UA_CreateSessionRequest req; UA_CreateSessionRequest_init(&req); UA_Session *newSession = NULL; lockServer(server); UA_StatusCode retval = UA_Session_create(server, NULL, &req, &newSession); UA_Boolean created = (newSession != NULL); if(newSession) UA_Session_remove(server, newSession, UA_SHUTDOWNREASON_CLOSE); unlockServer(server); cfg->maxSessions = orig; ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD); ck_assert(created); } END_TEST START_TEST(Server_createSession_maxSessions) { UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt16 orig = cfg->maxSessions; cfg->maxSessions = (UA_UInt16)server->sessionCount; LimitLogCapture capture = {false, false, false}; UA_Logger logger = {captureLimitLog, &capture, NULL}; UA_Logger *origLogger = cfg->logging; cfg->logging = &logger; /* The limit is reached by activated Sessions only. An un-activated Session * is purged to make room for the new Session instead of rejecting it. */ UA_SessionState *origStates = (UA_SessionState*) UA_malloc(server->sessionCount * sizeof(UA_SessionState)); ck_assert_ptr_nonnull(origStates); size_t sessionIdx = 0; session_list_entry *sentry; lockServer(server); LIST_FOREACH(sentry, &server->sessions, pointers) { origStates[sessionIdx++] = sentry->session.state; sentry->session.state = UA_SESSIONSTATE_ACTIVATED; } UA_CreateSessionRequest req; UA_CreateSessionRequest_init(&req); UA_Session *rejectedSession = NULL; UA_StatusCode retval = UA_Session_create(server, NULL, &req, &rejectedSession); sessionIdx = 0; LIST_FOREACH(sentry, &server->sessions, pointers) sentry->session.state = origStates[sessionIdx++]; unlockServer(server); UA_free(origStates); cfg->logging = origLogger; cfg->maxSessions = orig; ck_assert_uint_eq(retval, UA_STATUSCODE_BADTOOMANYSESSIONS); ck_assert_ptr_null(rejectedSession); #if UA_LOGLEVEL <= 400 ck_assert(capture.sessionLimit); #endif } END_TEST START_TEST(Server_createMonitoredItems_maxPerSubscription) { /* src/server/ua_services_monitoreditem.c:444-452 (Operation_CreateMonitoredItem): * if(!cmc->localMon && * (...maxMonitoredItemsPerSubscription exceeded...)) { * result->statusCode = UA_STATUSCODE_BADTOOMANYMONITOREDITEMS; * } * The "per subscription" branch is the more commonly-triggered * one (per-server limit is rarely hit). */ createSubscription(); createMonitoredItem(); UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxMonitoredItemsPerSubscription; cfg->maxMonitoredItemsPerSubscription = 1; /* already at the limit */ LimitLogCapture capture = {false, false, false}; UA_Logger logger = {captureLimitLog, &capture, NULL}; UA_Logger *origLogger = cfg->logging; cfg->logging = &logger; UA_CreateMonitoredItemsRequest req; UA_CreateMonitoredItemsRequest_init(&req); req.subscriptionId = subscriptionId; req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; req.itemsToCreateSize = 1; req.itemsToCreate = (UA_MonitoredItemCreateRequest*) UA_Array_new(1, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); UA_MonitoredItemCreateRequest_init(&req.itemsToCreate[0]); req.itemsToCreate[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); UA_CreateMonitoredItemsResponse resp; UA_CreateMonitoredItemsResponse_init(&resp); lockServer(server); Service_CreateMonitoredItems(server, session, &req, &resp); unlockServer(server); cfg->logging = origLogger; ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(resp.resultsSize, 1); ck_assert_uint_eq(resp.results[0].statusCode, UA_STATUSCODE_BADTOOMANYMONITOREDITEMS); #if UA_LOGLEVEL <= 400 ck_assert(capture.subscriptionMonitoredItemsLimit); #endif cfg->maxMonitoredItemsPerSubscription = orig; UA_Array_delete(req.itemsToCreate, 1, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); UA_CreateMonitoredItemsResponse_clear(&resp); } END_TEST START_TEST(Server_createMonitoredItems_maxPerServer) { createSubscription(); createMonitoredItem(); UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxMonitoredItems; cfg->maxMonitoredItems = 1; /* already at the server-wide limit */ LimitLogCapture capture = {false, false, false}; UA_Logger logger = {captureLimitLog, &capture, NULL}; UA_Logger *origLogger = cfg->logging; cfg->logging = &logger; UA_CreateMonitoredItemsRequest req; UA_CreateMonitoredItemsRequest_init(&req); req.subscriptionId = subscriptionId; req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; req.itemsToCreateSize = 1; req.itemsToCreate = (UA_MonitoredItemCreateRequest*) UA_Array_new(1, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); UA_MonitoredItemCreateRequest_init(&req.itemsToCreate[0]); req.itemsToCreate[0].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); UA_CreateMonitoredItemsResponse resp; UA_CreateMonitoredItemsResponse_init(&resp); lockServer(server); Service_CreateMonitoredItems(server, session, &req, &resp); unlockServer(server); cfg->logging = origLogger; ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(resp.resultsSize, 1); ck_assert_uint_eq(resp.results[0].statusCode, UA_STATUSCODE_BADTOOMANYMONITOREDITEMS); #if UA_LOGLEVEL <= 400 ck_assert(capture.serverMonitoredItemsLimit); #endif cfg->maxMonitoredItems = orig; UA_Array_delete(req.itemsToCreate, 1, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); UA_CreateMonitoredItemsResponse_clear(&resp); } END_TEST START_TEST(Server_createMonitoredItems_maxMonitoredItemsPerCall) { /* src/server/ua_services_monitoreditem.c:606-610 (Service_CreateMonitoredItems): * if(maxMonitoredItemsPerCall != 0 && * request->itemsToCreateSize > maxMonitoredItemsPerCall) { * response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS; * return true; * } */ createSubscription(); UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxMonitoredItemsPerCall; cfg->maxMonitoredItemsPerCall = 1; UA_CreateMonitoredItemsRequest req; UA_CreateMonitoredItemsRequest_init(&req); req.subscriptionId = subscriptionId; req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; req.itemsToCreateSize = 2; /* exceeds 1 */ req.itemsToCreate = (UA_MonitoredItemCreateRequest*) UA_Array_new(2, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); for(size_t i = 0; i < 2; i++) { UA_MonitoredItemCreateRequest_init(&req.itemsToCreate[i]); req.itemsToCreate[i].itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); } UA_CreateMonitoredItemsResponse resp; UA_CreateMonitoredItemsResponse_init(&resp); lockServer(server); Service_CreateMonitoredItems(server, session, &req, &resp); unlockServer(server); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADTOOMANYOPERATIONS); ck_assert_uint_eq(resp.resultsSize, 0); cfg->maxMonitoredItemsPerCall = orig; UA_Array_delete(req.itemsToCreate, 2, &UA_TYPES[UA_TYPES_MONITOREDITEMCREATEREQUEST]); UA_CreateMonitoredItemsResponse_clear(&resp); } END_TEST /* ==== Service_ModifyMonitoredItems / Service_SetMonitoringMode guards ==== */ START_TEST(Server_ModifyMonitoredItems_invalidTimestampsToReturn) { /* src/server/ua_services_monitoreditem.c:889-893: * if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) { * response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID; * return true; * } * The existing Server_modifyMonitoredItems test only uses the * happy path with valid timestampsToReturn. */ createSubscription(); createMonitoredItem(); UA_ModifyMonitoredItemsRequest req; UA_ModifyMonitoredItemsRequest_init(&req); req.subscriptionId = subscriptionId; req.timestampsToReturn = (UA_TimestampsToReturn)99; /* out of range */ req.itemsToModifySize = 1; req.itemsToModify = (UA_MonitoredItemModifyRequest*) UA_Array_new(1, &UA_TYPES[UA_TYPES_MONITOREDITEMMODIFYREQUEST]); UA_MonitoredItemModifyRequest_init(&req.itemsToModify[0]); req.itemsToModify[0].monitoredItemId = monitoredItemId; UA_ModifyMonitoredItemsResponse resp; UA_ModifyMonitoredItemsResponse_init(&resp); lockServer(server); Service_ModifyMonitoredItems(server, session, &req, &resp); unlockServer(server); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID); ck_assert_uint_eq(resp.resultsSize, 0); UA_Array_delete(req.itemsToModify, 1, &UA_TYPES[UA_TYPES_MONITOREDITEMMODIFYREQUEST]); UA_ModifyMonitoredItemsResponse_clear(&resp); } END_TEST START_TEST(Server_SetMonitoringMode_maxMonitoredItemsPerCall) { /* src/server/ua_services_monitoreditem.c:943-948: * if(maxMonitoredItemsPerCall != 0 && * request->monitoredItemIdsSize > maxMonitoredItemsPerCall) { * response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS; * } * The same guard exists in Service_CreateMonitoredItems (covered * by Server_createMonitoredItems_maxMonitoredItemsPerCall) but * the SetMonitoringMode path is a different code branch. */ createSubscription(); createMonitoredItem(); UA_ServerConfig *cfg = UA_Server_getConfig(server); UA_UInt32 orig = cfg->maxMonitoredItemsPerCall; cfg->maxMonitoredItemsPerCall = 1; UA_SetMonitoringModeRequest req; UA_SetMonitoringModeRequest_init(&req); req.subscriptionId = subscriptionId; req.monitoringMode = UA_MONITORINGMODE_REPORTING; req.monitoredItemIdsSize = 2; /* exceeds 1 */ req.monitoredItemIds = (UA_UInt32*) UA_Array_new(2, &UA_TYPES[UA_TYPES_UINT32]); req.monitoredItemIds[0] = monitoredItemId; req.monitoredItemIds[1] = monitoredItemId + 1; /* any value */ UA_SetMonitoringModeResponse resp; UA_SetMonitoringModeResponse_init(&resp); lockServer(server); Service_SetMonitoringMode(server, session, &req, &resp); unlockServer(server); ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADTOOMANYOPERATIONS); ck_assert_uint_eq(resp.resultsSize, 0); cfg->maxMonitoredItemsPerCall = orig; UA_Array_delete(req.monitoredItemIds, 2, &UA_TYPES[UA_TYPES_UINT32]); UA_SetMonitoringModeResponse_clear(&resp); } END_TEST START_TEST(Server_transferSubscription_keepsMonitoredItemsTree) { createSubscription(); UA_UInt32 ids[3]; createMonitoredItem(); ids[0] = monitoredItemId; createMonitoredItem(); ids[1] = monitoredItemId; createMonitoredItem(); ids[2] = monitoredItemId; /* Also authenticate the first session */ lockServer(server); UA_Subscription *oldSub = UA_Session_getSubscriptionById(session, subscriptionId); ck_assert_ptr_ne(oldSub, NULL); ck_assert_uint_eq(oldSub->monitoredItemsSize, 3); UA_String_clear(&session->clientUserIdOfSession); session->clientUserIdOfSession = UA_STRING_ALLOC("user1"); unlockServer(server); /* Create a second authenticated session */ UA_Session *session2 = createAuthenticatedSession("user1"); UA_TransferSubscriptionsRequest transferRequest; UA_TransferSubscriptionsRequest_init(&transferRequest); transferRequest.subscriptionIdsSize = 1; transferRequest.subscriptionIds = &subscriptionId; transferRequest.sendInitialValues = false; UA_TransferSubscriptionsResponse transferResponse; UA_TransferSubscriptionsResponse_init(&transferResponse); lockServer(server); Service_TransferSubscriptions(server, session2, &transferRequest, &transferResponse); ck_assert_uint_eq(transferResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(transferResponse.resultsSize, 1); ck_assert_uint_eq(transferResponse.results[0].statusCode, UA_STATUSCODE_GOOD); ck_assert_uint_eq(oldSub->monitoredItemsSize, 0); ck_assert_ptr_eq( ZIP_ROOT(&oldSub->monitoredItemsById), NULL); UA_Subscription *newSub = UA_Session_getSubscriptionById(session2, subscriptionId); ck_assert_ptr_ne(newSub, NULL); ck_assert_uint_eq(newSub->monitoredItemsSize, 3); ck_assert_ptr_ne( ZIP_ROOT(&newSub->monitoredItemsById), NULL); for(size_t i = 0; i < 3; i++) { UA_MonitoredItem *mon = ZIP_FIND(UA_MonitoredItemIdTree, &newSub->monitoredItemsById, &ids[i]); ck_assert_ptr_ne(mon, NULL); ck_assert_ptr_eq(mon->subscription, newSub); } unlockServer(server); /* Verify that the transferred tree is fully functional by deleting one * monitored item from the new subscription */ UA_DeleteMonitoredItemsRequest deleteRequest; UA_DeleteMonitoredItemsRequest_init(&deleteRequest); deleteRequest.subscriptionId = subscriptionId; deleteRequest.monitoredItemIdsSize = 1; deleteRequest.monitoredItemIds = &ids[0]; UA_DeleteMonitoredItemsResponse deleteResponse; UA_DeleteMonitoredItemsResponse_init(&deleteResponse); lockServer(server); Service_DeleteMonitoredItems(server, session2, &deleteRequest, &deleteResponse); unlockServer(server); ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(deleteResponse.resultsSize, 1); ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD); lockServer(server); ck_assert_uint_eq(newSub->monitoredItemsSize, 2); ck_assert_ptr_eq( ZIP_FIND(UA_MonitoredItemIdTree, &newSub->monitoredItemsById, &ids[0]), NULL); ck_assert_ptr_ne( ZIP_FIND(UA_MonitoredItemIdTree, &newSub->monitoredItemsById, &ids[1]), NULL); ck_assert_ptr_ne( ZIP_FIND(UA_MonitoredItemIdTree, &newSub->monitoredItemsById, &ids[2]), NULL); unlockServer(server); UA_DeleteMonitoredItemsResponse_clear(&deleteResponse); UA_TransferSubscriptionsResponse_clear(&transferResponse); lockServer(server); UA_Server_closeSession(server, &session2->sessionId); unlockServer(server); } END_TEST START_TEST(Server_deleteMonitoredItems_partial_keepsTreeConsistent) { createSubscription(); UA_UInt32 ids[3]; createMonitoredItem(); ids[0] = monitoredItemId; createMonitoredItem(); ids[1] = monitoredItemId; createMonitoredItem(); ids[2] = monitoredItemId; lockServer(server); UA_Subscription *sub = UA_Session_getSubscriptionById(session, subscriptionId); ck_assert_ptr_ne(sub, NULL); ck_assert_uint_eq(sub->monitoredItemsSize, 3); unlockServer(server); UA_DeleteMonitoredItemsRequest request; UA_DeleteMonitoredItemsRequest_init(&request); request.subscriptionId = subscriptionId; request.monitoredItemIdsSize = 1; request.monitoredItemIds = &ids[1]; UA_DeleteMonitoredItemsResponse response; UA_DeleteMonitoredItemsResponse_init(&response); lockServer(server); Service_DeleteMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 1); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); lockServer(server); ck_assert_uint_eq(sub->monitoredItemsSize, 2); ck_assert_ptr_eq( ZIP_FIND(UA_MonitoredItemIdTree, &sub->monitoredItemsById, &ids[1]), NULL); ck_assert_ptr_ne( ZIP_FIND(UA_MonitoredItemIdTree, &sub->monitoredItemsById, &ids[0]), NULL); ck_assert_ptr_ne( ZIP_FIND(UA_MonitoredItemIdTree, &sub->monitoredItemsById, &ids[2]), NULL); unlockServer(server); UA_UInt32 remainingIds[2]; remainingIds[0] = ids[0]; remainingIds[1] = ids[2]; request.monitoredItemIdsSize = 2; request.monitoredItemIds = remainingIds; UA_DeleteMonitoredItemsResponse_clear(&response); UA_DeleteMonitoredItemsResponse_init(&response); lockServer(server); Service_DeleteMonitoredItems(server, session, &request, &response); unlockServer(server); ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.resultsSize, 2); ck_assert_uint_eq(response.results[0], UA_STATUSCODE_GOOD); ck_assert_uint_eq(response.results[1], UA_STATUSCODE_GOOD); UA_DeleteMonitoredItemsResponse_clear(&response); } END_TEST /* Exercise removal and on-write iteration for several MonitoredItems attached * to the same node. */ static void nodeBpSampleCallback(UA_Server *s, UA_UInt32 monId, void *monCtx, const UA_NodeId *nid, void *nodeCtx, UA_UInt32 attrId, const UA_DataValue *value) { (void)s; (void)monId; (void)monCtx; (void)nid; (void)nodeCtx; (void)attrId; (void)value; } static size_t nodeMonitoredItemCount(UA_NodeId nodeId) { lockServer(server); const UA_Node *n = UA_NODESTORE_GET(server, &nodeId); ck_assert(n != NULL); size_t cnt = 0; for(UA_MonitoredItem *m = n->head.monitoredItems; m; m = m->nodeListNext) { cnt++; } UA_NODESTORE_RELEASE(server, n); unlockServer(server); return cnt; } START_TEST(Server_monitoredItems_sameNode_list) { /* A regular, writable variable node. */ UA_VariableAttributes attr = UA_VariableAttributes_default; UA_Int32 val = 1; UA_Variant_setScalar(&attr.value, &val, &UA_TYPES[UA_TYPES_INT32]); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_NodeId nodeId = UA_NODEID_STRING(1, "node.backpointer"); ck_assert_uint_eq(UA_Server_addVariableNode(server, nodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, "bp"), UA_NODEID_NULL, attr, NULL, NULL), UA_STATUSCODE_GOOD); /* Five local MonitoredItems with samplingInterval == 0 on the SAME node: * they all land on the per-node "on write" list. */ const size_t K = 5; UA_UInt32 ids[5]; UA_MonitoredItemCreateRequest item; UA_MonitoredItemCreateRequest_init(&item); item.itemToMonitor.nodeId = nodeId; item.itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE; item.monitoringMode = UA_MONITORINGMODE_REPORTING; item.requestedParameters.samplingInterval = 0.0; for(size_t i = 0; i < K; i++) { UA_MonitoredItemCreateResult r = UA_Server_createDataChangeMonitoredItem( server, UA_TIMESTAMPSTORETURN_NEITHER, item, NULL, nodeBpSampleCallback); ck_assert_uint_eq(r.statusCode, UA_STATUSCODE_GOOD); ids[i] = r.monitoredItemId; } ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), K); /* Write the node to run the on-write iterator over the whole list. */ UA_Int32 nv = 2; UA_Variant v; UA_Variant_setScalar(&v, &nv, &UA_TYPES[UA_TYPES_INT32]); ck_assert_uint_eq(UA_Server_writeValue(server, nodeId, v), UA_STATUSCODE_GOOD); ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), K); /* Remove items in a non-sequential order. */ ck_assert_uint_eq(UA_Server_deleteMonitoredItem(server, ids[2]), UA_STATUSCODE_GOOD); ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), K - 1); ck_assert_uint_eq(UA_Server_deleteMonitoredItem(server, ids[4]), UA_STATUSCODE_GOOD); ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), K - 2); ck_assert_uint_eq(UA_Server_deleteMonitoredItem(server, ids[0]), UA_STATUSCODE_GOOD); ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), K - 3); /* Write again with the shortened list, then remove the remainder. */ nv = 3; UA_Variant_setScalar(&v, &nv, &UA_TYPES[UA_TYPES_INT32]); ck_assert_uint_eq(UA_Server_writeValue(server, nodeId, v), UA_STATUSCODE_GOOD); ck_assert_uint_eq(UA_Server_deleteMonitoredItem(server, ids[1]), UA_STATUSCODE_GOOD); ck_assert_uint_eq(UA_Server_deleteMonitoredItem(server, ids[3]), UA_STATUSCODE_GOOD); ck_assert_uint_eq(nodeMonitoredItemCount(nodeId), 0); } END_TEST static Suite* testSuite_Client(void) { Suite *s = suite_create("Server Subscription"); TCase *tc_server = tcase_create("Server Subscription Basic"); tcase_add_checked_fixture(tc_server, setup, teardown); tcase_add_test(tc_server, Server_createSubscription); tcase_add_test(tc_server, Server_createSubscription_notificationCallback); tcase_add_test(tc_server, Server_closeSessionFromMonitoredItemCreated); tcase_add_test(tc_server, Server_republish_unknownSequenceNumber); tcase_add_test(tc_server, Server_createSubscription_maxSubscriptionsPerSession); tcase_add_test(tc_server, Server_createSession_unlimited); tcase_add_test(tc_server, Server_createSession_maxSessions); tcase_add_test(tc_server, Server_createMonitoredItems_maxPerSubscription); tcase_add_test(tc_server, Server_createMonitoredItems_maxPerServer); tcase_add_test(tc_server, Server_createMonitoredItems_maxMonitoredItemsPerCall); tcase_add_test(tc_server, Server_ModifyMonitoredItems_invalidTimestampsToReturn); tcase_add_test(tc_server, Server_SetMonitoringMode_maxMonitoredItemsPerCall); tcase_add_test(tc_server, Server_modifySubscription); tcase_add_test(tc_server, Server_setPublishingMode); tcase_add_test(tc_server, Server_negativeSamplingInterval); tcase_add_test(tc_server, Server_nanModifiedSamplingInterval); tcase_add_test(tc_server, Server_createMonitoredItems); tcase_add_test(tc_server, Server_modifyMonitoredItems); tcase_add_test(tc_server, Server_overflow); tcase_add_test(tc_server, Server_setMonitoringMode); tcase_add_test(tc_server, Server_deleteMonitoredItems); tcase_add_test(tc_server, Server_republish); tcase_add_test(tc_server, Server_republish_invalid); tcase_add_test(tc_server, Server_deleteSubscription); tcase_add_test(tc_server, Server_deleteSubscription_closeSessionFromMonitoredItemCallback); tcase_add_test(tc_server, Server_deleteSubscription_callbackSeesConsistentIndex); tcase_add_test(tc_server, Server_publishCallback); tcase_add_test(tc_server, Server_lifeTimeCount); tcase_add_test(tc_server, Server_invalidPublishingInterval); tcase_add_test(tc_server, Server_transferSubscriptionDiagnostics); #ifdef UA_ENABLE_DIAGNOSTICS tcase_add_test(tc_server, Server_diagnosticsRejectLongBrowseNames); #endif tcase_add_test(tc_server, Server_transferSubscription_anonymous); tcase_add_test(tc_server, Server_setTriggering_nothingToDo); tcase_add_test(tc_server, Server_setTriggering_invalidSubscription); tcase_add_test(tc_server, Server_setTriggering_invalidMonitoredItem); tcase_add_test(tc_server, Server_setTriggering_addAndRemoveLinks); tcase_add_test(tc_server, Server_setTriggering_maxMonitoredItemsPerCall); tcase_add_test(tc_server, Server_setTriggering_pruneDeletedLinks); tcase_add_test(tc_server, Server_modifySubscription_invalid); tcase_add_test(tc_server, Server_deleteSubscription_invalid); tcase_add_test(tc_server, Server_createMonitoredItems_invalidSubscription); tcase_add_test(tc_server, Server_setMonitoringMode_invalid); tcase_add_test(tc_server, Server_modifyMonitoredItems_invalidSubscription); tcase_add_test(tc_server, Server_deleteMonitoredItems_invalidSubscription); tcase_add_test(tc_server, Server_transferSubscription_sendInitialValues); tcase_add_test(tc_server, Server_transferSubscription_keepsMonitoredItemsTree); tcase_add_test(tc_server, Server_deleteMonitoredItems_partial_keepsTreeConsistent); tcase_add_test(tc_server, Server_subscriptionSurvivesSessionTimeoutTransferableToSameUser); tcase_add_test(tc_server, Server_subscriptionRecoverableWithOverride); tcase_add_test(tc_server, Server_transferSubscription_statusChangeWithNextPublish); tcase_add_test(tc_server, Server_detachedSubscription_anonymousInsecureNotTransferable); tcase_add_test(tc_server, Server_firstPublishingCycleNotBatched); tcase_add_test(tc_server, Server_dataSourceSamplingIntervalZero); tcase_add_test(tc_server, Server_monitoredItems_sameNode_list); suite_add_tcase(s, tc_server); 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; }