123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
- */
- #include "ua_server_internal.h"
- #include "ua_subscription.h"
- #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
- UA_StatusCode
- UA_MonitoredItem_removeNodeEventCallback(UA_Server *server, UA_Session *session,
- UA_Node *node, void *data) {
- /* data is the monitoredItemID */
- /* catch edge case that it's the first element */
- if (data == ((UA_ObjectNode *) node)->monitoredItemQueue) {
- ((UA_ObjectNode *)node)->monitoredItemQueue = ((UA_MonitoredItem *)data)->next;
- return UA_STATUSCODE_GOOD;
- }
- /* SLIST_FOREACH */
- for (UA_MonitoredItem *entry = ((UA_ObjectNode *) node)->monitoredItemQueue->next;
- entry != NULL; entry=entry->next) {
- if (entry == (UA_MonitoredItem *)data) {
- /* SLIST_REMOVE */
- UA_MonitoredItem *iter = ((UA_ObjectNode *) node)->monitoredItemQueue;
- for (; iter->next != entry; iter=iter->next) {}
- iter->next = entry->next;
- /* Unlike SLIST_REMOVE, do not free the entry, since it
- * is still being worked on in the calling function */
- break;
- }
- }
- return UA_STATUSCODE_GOOD;
- }
- typedef struct Events_nodeListElement {
- LIST_ENTRY(Events_nodeListElement) listEntry;
- UA_NodeId nodeId;
- } Events_nodeListElement;
- struct getNodesHandle {
- UA_Server *server;
- LIST_HEAD(Events_nodeList, Events_nodeListElement) nodes;
- };
- /* generates a unique event id */
- static UA_StatusCode
- UA_Event_generateEventId(UA_Server *server, UA_ByteString *generatedId) {
- /* EventId is a ByteString, which is basically just a string
- * We will use a 16-Byte ByteString as an identifier */
- generatedId->length = 16;
- generatedId->data = (UA_Byte *) UA_malloc(16 * sizeof(UA_Byte));
- if(!generatedId->data) {
- UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_USERLAND,
- "Server unable to allocate memory for EventId data.");
- return UA_STATUSCODE_BADOUTOFMEMORY;
- }
- /* GUIDs are unique, have a size of 16 byte and already have
- * a generator so use that.
- * Make sure GUIDs really do have 16 byte, in case someone may
- * have changed that struct */
- UA_assert(sizeof(UA_Guid) == 16);
- UA_Guid tmpGuid = UA_Guid_random();
- memcpy(generatedId->data, &tmpGuid, 16);
- return UA_STATUSCODE_GOOD;
- }
- UA_StatusCode
- UA_Server_createEvent(UA_Server *server, const UA_NodeId eventType, UA_NodeId *outNodeId) {
- if(!outNodeId) {
- UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND, "outNodeId cannot be NULL!");
- return UA_STATUSCODE_BADINVALIDARGUMENT;
- }
- /* Make sure the eventType is a subtype of BaseEventType */
- UA_NodeId hasSubtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
- UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
- if(!isNodeInTree(&server->config.nodestore, &eventType, &baseEventTypeId, &hasSubtypeId, 1)) {
- UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND,
- "Event type must be a subtype of BaseEventType!");
- return UA_STATUSCODE_BADINVALIDARGUMENT;
- }
- /* Create an ObjectNode which represents the event */
- UA_QualifiedName name;
- UA_QualifiedName_init(&name);
- UA_NodeId newNodeId = UA_NODEID_NULL;
- UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
- UA_StatusCode retval =
- UA_Server_addObjectNode(server,
- UA_NODEID_NULL, /* Set a random unused NodeId */
- UA_NODEID_NULL, /* No parent */
- UA_NODEID_NULL, /* No parent reference */
- name, /* an event does not have a name */
- eventType, /* the type of the event */
- oAttr, /* default attributes are fine */
- NULL, /* no node context */
- &newNodeId);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND,
- "Adding event failed. StatusCode %s", UA_StatusCode_name(retval));
- return retval;
- }
- /* Find the eventType variable */
- name = UA_QUALIFIEDNAME(0, "EventType");
- UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server, newNodeId, 1, &name);
- if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
- retval = bpr.statusCode;
- UA_BrowsePathResult_deleteMembers(&bpr);
- UA_Server_deleteNode(server, newNodeId, true);
- UA_NodeId_deleteMembers(&newNodeId);
- return retval;
- }
- /* Set the EventType */
- UA_Variant value;
- UA_Variant_init(&value);
- UA_Variant_setScalar(&value, (void*)(uintptr_t)&eventType, &UA_TYPES[UA_TYPES_NODEID]);
- retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
- UA_BrowsePathResult_deleteMembers(&bpr);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_Server_deleteNode(server, newNodeId, true);
- UA_NodeId_deleteMembers(&newNodeId);
- return retval;
- }
- *outNodeId = newNodeId;
- return UA_STATUSCODE_GOOD;
- }
- static UA_Boolean
- isValidEvent(UA_Server *server, const UA_NodeId *validEventParent, const UA_NodeId *eventId) {
- /* find the eventType variableNode */
- UA_QualifiedName findName = UA_QUALIFIEDNAME(0, "EventType");
- UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server, *eventId, 1, &findName);
- if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
- UA_BrowsePathResult_deleteMembers(&bpr);
- return UA_FALSE;
- }
-
- /* get the EventType Property Node */
- const UA_Node* tLeafNode = UA_Nodestore_get(server, &bpr.targets[0].targetId.nodeId);
- UA_Variant tOutVariant;
- /* read the Value of EventType Property Node (the Value should be a NodeId) */
- UA_Server_readValue(server, tLeafNode->nodeId, &tOutVariant);
- UA_NodeId tEventType = *((UA_NodeId*)tOutVariant.data);
- /* Make sure the EventType is not a Subtype of CondtionType
- * First check for filter set using UaExpert
- * (ConditionId Clause won't be present in Events, which are not Conditions)
- * Second check for Events which are Conditions or Alarms (Part 9 not supported yet) */
- UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE);
- UA_NodeId hasSubtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
- if(UA_NodeId_equal(validEventParent, &conditionTypeId) ||
- isNodeInTree(&server->config.nodestore, &tEventType,
- &conditionTypeId, &hasSubtypeId, 1)){
- UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND,
- "Alarms and Conditions are not supported yet!");
- UA_Nodestore_release(server, (const UA_Node *) tLeafNode);
- UA_BrowsePathResult_deleteMembers(&bpr);
- return UA_FALSE;
- }
- /* check whether Valid Event other than Conditions */
- UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
- UA_Boolean tmp = isNodeInTree(&server->config.nodestore, &tEventType,
- &baseEventTypeId, &hasSubtypeId, 1);
- UA_Nodestore_release(server, (const UA_Node *) tLeafNode);
- UA_BrowsePathResult_deleteMembers(&bpr);
- return tmp;
- }
- /* static UA_StatusCode */
- /* whereClausesApply(UA_Server *server, const UA_ContentFilter whereClause, */
- /* UA_EventFieldList *efl, UA_Boolean *result) { */
- /* /\* if the where clauses aren't specified leave everything as is *\/ */
- /* if(whereClause.elementsSize == 0) { */
- /* *result = UA_TRUE; */
- /* return UA_STATUSCODE_GOOD; */
- /* } */
- /* /\* where clauses were specified *\/ */
- /* UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_USERLAND, */
- /* "Where clauses are not supported by the server."); */
- /* *result = UA_TRUE; */
- /* return UA_STATUSCODE_BADNOTSUPPORTED; */
- /* } */
- /* Part 4: 7.4.4.5 SimpleAttributeOperand
- * The clause can point to any attribute of nodes. Either a child of the event
- * node and also the event type. */
- static UA_StatusCode
- resolveSimpleAttributeOperand(UA_Server *server, UA_Session *session, const UA_NodeId *origin,
- const UA_SimpleAttributeOperand *sao, UA_Variant *value) {
- /* Prepare the ReadValueId */
- UA_ReadValueId rvi;
- UA_ReadValueId_init(&rvi);
- rvi.indexRange = sao->indexRange;
- rvi.attributeId = sao->attributeId;
- /* If this list (browsePath) is empty the Node is the instance of the
- * TypeDefinition. */
- if(sao->browsePathSize == 0) {
- rvi.nodeId = sao->typeDefinitionId;
- UA_DataValue v = UA_Server_readWithSession(server, session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
- if(v.status == UA_STATUSCODE_GOOD && v.hasValue)
- *value = v.value;
- return v.status;
- }
- /* Resolve the browse path */
- UA_BrowsePathResult bpr =
- UA_Server_browseSimplifiedBrowsePath(server, *origin, sao->browsePathSize, sao->browsePath);
- if(bpr.targetsSize == 0 && bpr.statusCode == UA_STATUSCODE_GOOD)
- bpr.statusCode = UA_STATUSCODE_BADNOTFOUND;
- if(bpr.statusCode != UA_STATUSCODE_GOOD) {
- UA_StatusCode retval = bpr.statusCode;
- UA_BrowsePathResult_deleteMembers(&bpr);
- return retval;
- }
- /* Read the first matching element. Move the value to the output. */
- rvi.nodeId = bpr.targets[0].targetId.nodeId;
- UA_DataValue v = UA_Server_readWithSession(server, session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
- if(v.status == UA_STATUSCODE_GOOD && v.hasValue)
- *value = v.value;
- UA_BrowsePathResult_deleteMembers(&bpr);
- return v.status;
- }
- /* filters the given event with the given filter and writes the results into a notification */
- static UA_StatusCode
- UA_Server_filterEvent(UA_Server *server, UA_Session *session,
- const UA_NodeId *eventNode, UA_EventFilter *filter,
- UA_EventNotification *notification) {
- if (filter->selectClausesSize == 0)
- return UA_STATUSCODE_BADEVENTFILTERINVALID;
- /* setup */
- UA_EventFieldList_init(¬ification->fields);
- /* EventFilterResult isn't being used currently
- UA_EventFilterResult_init(¬ification->result); */
- notification->fields.eventFieldsSize = filter->selectClausesSize;
- notification->fields.eventFields = (UA_Variant *) UA_Array_new(notification->fields.eventFieldsSize,
- &UA_TYPES[UA_TYPES_VARIANT]);
- if (!notification->fields.eventFields) {
- /* EventFilterResult currently isn't being used
- UA_EventFiterResult_deleteMembers(¬ification->result); */
- return UA_STATUSCODE_BADOUTOFMEMORY;
- }
- /* EventFilterResult currently isn't being used
- notification->result.selectClauseResultsSize = filter->selectClausesSize;
- notification->result.selectClauseResults = (UA_StatusCode *)
- UA_Array_new(filter->selectClausesSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
- if (!notification->result->selectClauseResults) {
- UA_EventFieldList_deleteMembers(¬ification->fields);
- UA_EventFilterResult_deleteMembers(¬ification->result);
- return UA_STATUSCODE_BADOUTOFMEMORY;
- }
- */
- /* ================ apply the filter ===================== */
- /* check if the browsePath is BaseEventType, in which case nothing more needs to be checked */
- UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
- /* iterate over the selectClauses */
- for(size_t i = 0; i < filter->selectClausesSize; i++) {
- if(!UA_NodeId_equal(&filter->selectClauses[i].typeDefinitionId, &baseEventTypeId) &&
- !isValidEvent(server, &filter->selectClauses[i].typeDefinitionId, eventNode)) {
- UA_Variant_init(¬ification->fields.eventFields[i]);
- /* EventFilterResult currently isn't being used
- notification->result.selectClauseResults[i] = UA_STATUSCODE_BADTYPEDEFINITIONINVALID; */
- continue;
- }
- /* TODO: Put the result into the selectClausResults */
- resolveSimpleAttributeOperand(server, session, eventNode,
- &filter->selectClauses[i],
- ¬ification->fields.eventFields[i]);
- }
- /* UA_Boolean whereClauseResult = UA_TRUE; */
- /* return whereClausesApply(server, filter->whereClause, ¬ification->fields, &whereClauseResult); */
- return UA_STATUSCODE_GOOD;
- }
- static UA_StatusCode
- eventSetStandardFields(UA_Server *server, const UA_NodeId *event,
- const UA_NodeId *origin, UA_ByteString *outEventId) {
- /* Set the SourceNode */
- UA_StatusCode retval;
- UA_QualifiedName name = UA_QUALIFIEDNAME(0, "SourceNode");
- UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server, *event, 1, &name);
- if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
- retval = bpr.statusCode;
- UA_BrowsePathResult_deleteMembers(&bpr);
- return retval;
- }
- UA_Variant value;
- UA_Variant_init(&value);
- UA_Variant_setScalarCopy(&value, origin, &UA_TYPES[UA_TYPES_NODEID]);
- retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
- UA_Variant_deleteMembers(&value);
- UA_BrowsePathResult_deleteMembers(&bpr);
- if(retval != UA_STATUSCODE_GOOD)
- return retval;
- /* Set the ReceiveTime */
- name = UA_QUALIFIEDNAME(0, "ReceiveTime");
- bpr = UA_Server_browseSimplifiedBrowsePath(server, *event, 1, &name);
- if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
- retval = bpr.statusCode;
- UA_BrowsePathResult_deleteMembers(&bpr);
- return retval;
- }
- UA_DateTime time = UA_DateTime_now();
- UA_Variant_setScalar(&value, &time, &UA_TYPES[UA_TYPES_DATETIME]);
- retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
- UA_BrowsePathResult_deleteMembers(&bpr);
- if(retval != UA_STATUSCODE_GOOD)
- return retval;
- /* Set the EventId */
- UA_ByteString eventId = UA_BYTESTRING_NULL;
- retval = UA_Event_generateEventId(server, &eventId);
- if(retval != UA_STATUSCODE_GOOD)
- return retval;
- name = UA_QUALIFIEDNAME(0, "EventId");
- bpr = UA_Server_browseSimplifiedBrowsePath(server, *event, 1, &name);
- if(bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1) {
- retval = bpr.statusCode;
- UA_ByteString_deleteMembers(&eventId);
- UA_BrowsePathResult_deleteMembers(&bpr);
- return retval;
- }
- UA_Variant_init(&value);
- UA_Variant_setScalar(&value, &eventId, &UA_TYPES[UA_TYPES_BYTESTRING]);
- retval = UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
- UA_BrowsePathResult_deleteMembers(&bpr);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_ByteString_deleteMembers(&eventId);
- return retval;
- }
- /* Return the EventId */
- if(outEventId)
- *outEventId = eventId;
- else
- UA_ByteString_deleteMembers(&eventId);
- return UA_STATUSCODE_GOOD;
- }
- /* Insert each node into the list (passed as handle) */
- static UA_StatusCode
- getParentsNodeIteratorCallback(UA_NodeId parentId, UA_Boolean isInverse,
- UA_NodeId referenceTypeId, struct getNodesHandle *handle) {
- /* Parents have an inverse reference */
- if(!isInverse)
- return UA_STATUSCODE_GOOD;
- /* Is this a hierarchical reference? */
- if(!isNodeInTree(&handle->server->config.nodestore, &referenceTypeId,
- &hierarchicalReferences, &subtypeId, 1))
- return UA_STATUSCODE_GOOD;
- Events_nodeListElement *entry = (Events_nodeListElement *) UA_malloc(sizeof(Events_nodeListElement));
- if(!entry)
- return UA_STATUSCODE_BADOUTOFMEMORY;
- UA_StatusCode retval = UA_NodeId_copy(&parentId, &entry->nodeId);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_free(entry);
- return retval;
- }
- LIST_INSERT_HEAD(&handle->nodes, entry, listEntry);
- /* Recursion */
- UA_Server_forEachChildNodeCall(handle->server, parentId, (UA_NodeIteratorCallback)getParentsNodeIteratorCallback, handle);
- return UA_STATUSCODE_GOOD;
- }
- /* Filters an event according to the filter specified by mon and then adds it to
- * mons notification queue */
- static UA_StatusCode
- UA_Event_addEventToMonitoredItem(UA_Server *server, const UA_NodeId *event,
- UA_MonitoredItem *mon) {
- UA_Notification *notification = (UA_Notification *) UA_malloc(sizeof(UA_Notification));
- if(!notification)
- return UA_STATUSCODE_BADOUTOFMEMORY;
- /* Get the session */
- UA_Subscription *sub = mon->subscription;
- UA_Session *session = sub->session;
- /* Apply the filter */
- UA_StatusCode retval = UA_Server_filterEvent(server, session, event,
- &mon->filter.eventFilter,
- ¬ification->data.event);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_free(notification);
- return retval;
- }
- /* Enqueue the notification */
- notification->mon = mon;
- UA_Notification_enqueue(server, mon->subscription, mon, notification);
- return UA_STATUSCODE_GOOD;
- }
- static const UA_NodeId objectsFolderId = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_OBJECTSFOLDER}};
- static const UA_NodeId parentReferences_events[2] =
- {{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ORGANIZES}},
- {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASCOMPONENT}}};
- UA_StatusCode
- UA_Server_triggerEvent(UA_Server *server, const UA_NodeId eventNodeId, const UA_NodeId origin,
- UA_ByteString *outEventId, const UA_Boolean deleteEventNode) {
- /* Make sure the origin is in the ObjectsFolder (TODO: or in the ViewsFolder) */
- UA_NodeId *parentTypeHierachy = NULL;
- size_t parentTypeHierachySize = 0;
- getTypesHierarchy(&server->config.nodestore, parentReferences_events, 2,
- &parentTypeHierachy, &parentTypeHierachySize, UA_TRUE);
- UA_Boolean isInObjectsFolder = isNodeInTree(&server->config.nodestore, &origin, &objectsFolderId, parentTypeHierachy, parentTypeHierachySize);
- UA_Array_delete(parentTypeHierachy, parentTypeHierachySize, &UA_TYPES[UA_TYPES_NODEID]);
- if (!isInObjectsFolder) {
- UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_USERLAND,
- "Node for event must be in ObjectsFolder!");
- return UA_STATUSCODE_BADINVALIDARGUMENT;
- }
- UA_StatusCode retval = eventSetStandardFields(server, &eventNodeId, &origin, outEventId);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
- "Events: Could not set the standard event fields with StatusCode %s",
- UA_StatusCode_name(retval));
- return retval;
- }
- /* Get an array with all parents. The first call to
- * getParentsNodeIteratorCallback adds the emitting node itself. */
- struct getNodesHandle parentHandle;
- parentHandle.server = server;
- LIST_INIT(&parentHandle.nodes);
- retval = getParentsNodeIteratorCallback(origin, UA_TRUE, parentReferences_events[1], &parentHandle);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
- "Events: Could not create the list of nodes listening on the event with StatusCode %s",
- UA_StatusCode_name(retval));
- return retval;
- }
- /* Add the event to each node's monitored items */
- Events_nodeListElement *parentIter, *tmp_parentIter;
- LIST_FOREACH_SAFE(parentIter, &parentHandle.nodes, listEntry, tmp_parentIter) {
- const UA_ObjectNode *node = (const UA_ObjectNode *) UA_Nodestore_get(server, &parentIter->nodeId);
- if(node->nodeClass == UA_NODECLASS_OBJECT) {
- for(UA_MonitoredItem *monIter = node->monitoredItemQueue; monIter != NULL; monIter = monIter->next) {
- retval = UA_Event_addEventToMonitoredItem(server, &eventNodeId, monIter);
- if(retval != UA_STATUSCODE_GOOD) {
- UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
- "Events: Could not add the event to a listening node with StatusCode %s",
- UA_StatusCode_name(retval));
- }
- }
- }
- UA_Nodestore_release(server, (const UA_Node *) node);
- LIST_REMOVE(parentIter, listEntry);
- UA_NodeId_deleteMembers(&parentIter->nodeId);
- UA_free(parentIter);
- }
- /* Delete the node representation of the event */
- if(deleteEventNode) {
- retval = UA_Server_deleteNode(server, eventNodeId, UA_TRUE);
- if (retval != UA_STATUSCODE_GOOD) {
- UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
- "Attempt to remove event using deleteNode failed. StatusCode %s",
- UA_StatusCode_name(retval));
- return retval;
- }
- }
- return UA_STATUSCODE_GOOD;
- }
- #endif /* UA_ENABLE_SUBSCRIPTIONS_EVENTS */
|