static void checkSignature(const UA_Server *server, const UA_SecureChannel *channel, UA_Session *session, const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response) { if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; const UA_ByteString *const localCertificate = &securityPolicy->localCertificate; UA_ByteString dataToVerify; UA_StatusCode retval = UA_ByteString_allocBuffer(&dataToVerify, localCertificate->length + session->serverNonce.length); if(retval != UA_STATUSCODE_GOOD) { response->responseHeader.serviceResult = retval; UA_LOG_DEBUG_SESSION(server->config.logger, session, "Failed to allocate buffer for signature verification! %#10x", retval); return; } memcpy(dataToVerify.data, localCertificate->data, localCertificate->length); memcpy(dataToVerify.data + localCertificate->length, session->serverNonce.data, session->serverNonce.length); retval = securityPolicy->asymmetricModule.cryptoModule. verify(securityPolicy, channel->channelContext, &dataToVerify, &request->clientSignature.signature); if(retval != UA_STATUSCODE_GOOD) { response->responseHeader.serviceResult = retval; UA_LOG_DEBUG_SESSION(server->config.logger, session, "Failed to verify the client signature! %#10x", retval); UA_ByteString_deleteMembers(&dataToVerify); return; } retval = UA_SecureChannel_generateNonce(channel, 32, &response->serverNonce); retval |= UA_ByteString_copy(&response->serverNonce, &session->serverNonce); if(retval != UA_STATUSCODE_GOOD) { response->responseHeader.serviceResult = retval; UA_LOG_DEBUG_SESSION(server->config.logger, session, "Failed to generate a new nonce! %#10x", retval); UA_ByteString_deleteMembers(&dataToVerify); return; } UA_ByteString_deleteMembers(&dataToVerify); } }
void Service_ModifySubscription(UA_Server *server, UA_Session *session, const UA_ModifySubscriptionRequest *request, UA_ModifySubscriptionResponse *response) { UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Processing ModifySubscriptionRequest"); UA_Subscription *sub = UA_Session_getSubscriptionById(session, request->subscriptionId); if(!sub) { response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID; return; } UA_StatusCode retval = setSubscriptionSettings(server, sub, request->requestedPublishingInterval, request->requestedLifetimeCount, request->requestedMaxKeepAliveCount, request->maxNotificationsPerPublish, request->priority); if(retval != UA_STATUSCODE_GOOD) { response->responseHeader.serviceResult = retval; return; } sub->currentLifetimeCount = 0; /* Reset the subscription lifetime */ response->revisedPublishingInterval = sub->publishingInterval; response->revisedLifetimeCount = sub->lifeTimeCount; response->revisedMaxKeepAliveCount = sub->maxKeepAliveCount; }
void Service_SetPublishingMode(UA_Server *server, UA_Session *session, const UA_SetPublishingModeRequest *request, UA_SetPublishingModeResponse *response) { UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Processing SetPublishingModeRequest"); UA_Boolean publishingEnabled = request->publishingEnabled; /* request is const */ response->responseHeader.serviceResult = UA_Server_processServiceOperations(server, session, (UA_ServiceOperation)Operation_SetPublishingMode, &publishingEnabled, &request->subscriptionIdsSize, &UA_TYPES[UA_TYPES_UINT32], &response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]); }
void Service_Write(UA_Server *server, UA_Session *session, const UA_WriteRequest *request, UA_WriteResponse *response) { UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing WriteRequest"); response->responseHeader.serviceResult = UA_Server_processServiceOperations(server, session, (UA_ServiceOperation)Operation_Write, &request->nodesToWriteSize, &UA_TYPES[UA_TYPES_WRITEVALUE], &response->resultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]); }
void UA_MonitoredItem_sampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem) { UA_Subscription *sub = monitoredItem->subscription; UA_Session *session = &server->adminSession; if(sub) session = sub->session; UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Subscription %u | " "MonitoredItem %i | Sample callback called", sub ? sub->subscriptionId : 0, monitoredItem->monitoredItemId); UA_assert(monitoredItem->attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER); /* Get the node */ const UA_Node *node = UA_Nodestore_getNode(server->nsCtx, &monitoredItem->monitoredNodeId); /* Sample the value. The sample can still point into the node. */ UA_DataValue value; UA_DataValue_init(&value); if(node) { UA_ReadValueId rvid; UA_ReadValueId_init(&rvid); rvid.nodeId = monitoredItem->monitoredNodeId; rvid.attributeId = monitoredItem->attributeId; rvid.indexRange = monitoredItem->indexRange; ReadWithNode(node, server, session, monitoredItem->timestampsToReturn, &rvid, &value); } else { value.hasStatus = true; value.status = UA_STATUSCODE_BADNODEIDUNKNOWN; } /* Operate on the sample */ UA_Boolean movedValue = false; UA_StatusCode retval = sampleCallbackWithValue(server, session, sub, monitoredItem, &value, &movedValue); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_SESSION(&server->config.logger, session, "Subscription %u | " "MonitoredItem %i | Sampling returned the statuscode %s", sub ? sub->subscriptionId : 0, monitoredItem->monitoredItemId, UA_StatusCode_name(retval)); } /* Delete the sample if it was not moved to the notification. */ if(!movedValue) UA_DataValue_deleteMembers(&value); /* Does nothing for UA_VARIANT_DATA_NODELETE */ if(node) UA_Nodestore_releaseNode(server->nsCtx, node); }
void Service_CreateSubscription(UA_Server *server, UA_Session *session, const UA_CreateSubscriptionRequest *request, UA_CreateSubscriptionResponse *response) { /* Check limits for the number of subscriptions */ if((server->config.maxSubscriptionsPerSession != 0) && (session->numSubscriptions >= server->config.maxSubscriptionsPerSession)) { response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYSUBSCRIPTIONS; return; } /* Create the subscription */ UA_Subscription *newSubscription = UA_Subscription_new(session, response->subscriptionId); if(!newSubscription) { UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Processing CreateSubscriptionRequest failed"); response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; return; } UA_Session_addSubscription(session, newSubscription); /* Also assigns the subscription id */ /* Set the subscription parameters */ newSubscription->publishingEnabled = request->publishingEnabled; UA_StatusCode retval = setSubscriptionSettings(server, newSubscription, request->requestedPublishingInterval, request->requestedLifetimeCount, request->requestedMaxKeepAliveCount, request->maxNotificationsPerPublish, request->priority); if(retval != UA_STATUSCODE_GOOD) { response->responseHeader.serviceResult = retval; return; } newSubscription->currentKeepAliveCount = newSubscription->maxKeepAliveCount; /* set settings first */ /* Prepare the response */ response->subscriptionId = newSubscription->subscriptionId; response->revisedPublishingInterval = newSubscription->publishingInterval; response->revisedLifetimeCount = newSubscription->lifeTimeCount; response->revisedMaxKeepAliveCount = newSubscription->maxKeepAliveCount; UA_LOG_INFO_SESSION(&server->config.logger, session, "Subscription %u | " "Created the Subscription with a publishing interval of %.2f ms", response->subscriptionId, newSubscription->publishingInterval); }
void Service_Call(UA_Server *server, UA_Session *session, const UA_CallRequest *request, UA_CallResponse *response) { UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CallRequest"); if(request->methodsToCallSize <= 0) { response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO; return; } response->results = UA_Array_new(request->methodsToCallSize, &UA_TYPES[UA_TYPES_CALLMETHODRESULT]); if(!response->results) { response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; return; } response->resultsSize = request->methodsToCallSize; for(size_t i = 0; i < request->methodsToCallSize;i++) Service_Call_single(server, session, &request->methodsToCall[i], &response->results[i]); }
static UA_StatusCode setSubscriptionSettings(UA_Server *server, UA_Subscription *subscription, UA_Double requestedPublishingInterval, UA_UInt32 requestedLifetimeCount, UA_UInt32 requestedMaxKeepAliveCount, UA_UInt32 maxNotificationsPerPublish, UA_Byte priority) { /* deregister the callback if required */ Subscription_unregisterPublishCallback(server, subscription); /* re-parameterize the subscription */ subscription->publishingInterval = requestedPublishingInterval; UA_BOUNDEDVALUE_SETWBOUNDS(server->config.publishingIntervalLimits, requestedPublishingInterval, subscription->publishingInterval); /* check for nan*/ if(requestedPublishingInterval != requestedPublishingInterval) subscription->publishingInterval = server->config.publishingIntervalLimits.min; UA_BOUNDEDVALUE_SETWBOUNDS(server->config.keepAliveCountLimits, requestedMaxKeepAliveCount, subscription->maxKeepAliveCount); UA_BOUNDEDVALUE_SETWBOUNDS(server->config.lifeTimeCountLimits, requestedLifetimeCount, subscription->lifeTimeCount); if(subscription->lifeTimeCount < 3 * subscription->maxKeepAliveCount) subscription->lifeTimeCount = 3 * subscription->maxKeepAliveCount; subscription->notificationsPerPublish = maxNotificationsPerPublish; if(maxNotificationsPerPublish == 0 || maxNotificationsPerPublish > server->config.maxNotificationsPerPublish) subscription->notificationsPerPublish = server->config.maxNotificationsPerPublish; subscription->priority = priority; UA_StatusCode retval = Subscription_registerPublishCallback(server, subscription); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_DEBUG_SESSION(&server->config.logger, subscription->session, "Subscription %u | Could not register publish callback with error code %s", subscription->subscriptionId, UA_StatusCode_name(retval)); return retval; } return UA_STATUSCODE_GOOD; }
void Service_Read(UA_Server *server, UA_Session *session, const UA_ReadRequest *request, UA_ReadResponse *response) { UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ReadRequest"); /* Check if the timestampstoreturn is valid */ op_timestampsToReturn = request->timestampsToReturn; if(op_timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) { response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID; return; } /* Check if maxAge is valid */ if(request->maxAge < 0) { response->responseHeader.serviceResult = UA_STATUSCODE_BADMAXAGEINVALID; return; } response->responseHeader.serviceResult = UA_Server_processServiceOperations(server, session, (UA_ServiceOperation)Operation_Read, &request->nodesToReadSize, &UA_TYPES[UA_TYPES_READVALUEID], &response->resultsSize, &UA_TYPES[UA_TYPES_DATAVALUE]); }
/* movedValue returns whether the sample was moved to the notification. The * default is false. */ static UA_StatusCode sampleCallbackWithValue(UA_Server *server, UA_Session *session, UA_Subscription *sub, UA_MonitoredItem *mon, UA_DataValue *value, UA_Boolean *movedValue) { UA_assert(mon->attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER); /* Contains heap-allocated binary encoding of the value if a change was detected */ UA_ByteString binValueEncoding = UA_BYTESTRING_NULL; /* Has the value changed? Allocates memory in binValueEncoding if necessary. * value is edited internally so we make a shallow copy. */ UA_Boolean changed = false; UA_StatusCode retval = detectValueChange(server, mon, *value, &binValueEncoding, &changed); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_SESSION(&server->config.logger, session, "Subscription %u | " "MonitoredItem %i | Value change detection failed with StatusCode %s", sub ? sub->subscriptionId : 0, mon->monitoredItemId, UA_StatusCode_name(retval)); return retval; } if(!changed) { UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Subscription %u | " "MonitoredItem %i | The value has not changed", sub ? sub->subscriptionId : 0, mon->monitoredItemId); return UA_STATUSCODE_GOOD; } /* The MonitoredItem is attached to a subscription (not server-local). * Prepare a notification and enqueue it. */ if(sub) { /* Allocate a new notification */ UA_Notification *newNotification = (UA_Notification *)UA_malloc(sizeof(UA_Notification)); if(!newNotification) { UA_ByteString_deleteMembers(&binValueEncoding); return UA_STATUSCODE_BADOUTOFMEMORY; } if(value->value.storageType == UA_VARIANT_DATA) { newNotification->data.value = *value; /* Move the value to the notification */ *movedValue = true; } else { /* => (value->value.storageType == UA_VARIANT_DATA_NODELETE) */ retval = UA_DataValue_copy(value, &newNotification->data.value); if(retval != UA_STATUSCODE_GOOD) { UA_ByteString_deleteMembers(&binValueEncoding); UA_free(newNotification); return retval; } } /* <-- Point of no return --> */ UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Subscription %u | " "MonitoredItem %i | Enqueue a new notification", sub ? sub->subscriptionId : 0, mon->monitoredItemId); newNotification->mon = mon; UA_Notification_enqueue(server, sub, mon, newNotification); } /* Store the encoding for comparison */ UA_ByteString_deleteMembers(&mon->lastSampledValue); mon->lastSampledValue = binValueEncoding; /* Store the value for filter comparison (we don't want to decode * lastSampledValue in every iteration). Don't test the return code here. If * this fails, lastValue is empty and a notification will be forced for the * next deadband comparison. */ if((mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_NONE || mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_ABSOLUTE || mon->filter.dataChangeFilter.deadbandType == UA_DEADBANDTYPE_PERCENT) && (mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUS || mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUE || mon->filter.dataChangeFilter.trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP)) { UA_Variant_deleteMembers(&mon->lastValue); UA_Variant_copy(&value->value, &mon->lastValue); #ifdef UA_ENABLE_DA UA_StatusCode_deleteMembers(&mon->lastStatus); UA_StatusCode_copy(&value->status, &mon->lastStatus); #endif } /* Call the local callback if the MonitoredItem is not attached to a * subscription. Do this at the very end. Because the callback might delete * the subscription. */ if(!sub) { UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*) mon; void *nodeContext = NULL; UA_Server_getNodeContext(server, mon->monitoredNodeId, &nodeContext); localMon->callback.dataChangeCallback(server, mon->monitoredItemId, localMon->context, &mon->monitoredNodeId, nodeContext, mon->attributeId, value); } return UA_STATUSCODE_GOOD; }
static void Operation_Read(UA_Server *server, UA_Session *session, const UA_ReadValueId *id, UA_DataValue *v) { UA_LOG_DEBUG_SESSION(server->config.logger, session, "Read the attribute %i", id->attributeId); /* XML encoding is not supported */ if(id->dataEncoding.name.length > 0 && !UA_String_equal(&binEncoding, &id->dataEncoding.name)) { v->hasStatus = true; v->status = UA_STATUSCODE_BADDATAENCODINGUNSUPPORTED; return; } /* Index range for an attribute other than value */ if(id->indexRange.length > 0 && id->attributeId != UA_ATTRIBUTEID_VALUE) { v->hasStatus = true; v->status = UA_STATUSCODE_BADINDEXRANGENODATA; return; } /* Get the node */ const UA_Node *node = UA_Nodestore_get(server, &id->nodeId); if(!node) { v->hasStatus = true; v->status = UA_STATUSCODE_BADNODEIDUNKNOWN; return; } /* Read the attribute */ UA_StatusCode retval = UA_STATUSCODE_GOOD; switch(id->attributeId) { case UA_ATTRIBUTEID_NODEID: retval = UA_Variant_setScalarCopy(&v->value, &node->nodeId, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_NODECLASS: retval = UA_Variant_setScalarCopy(&v->value, &node->nodeClass, &UA_TYPES[UA_TYPES_NODECLASS]); break; case UA_ATTRIBUTEID_BROWSENAME: retval = UA_Variant_setScalarCopy(&v->value, &node->browseName, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]); break; case UA_ATTRIBUTEID_DISPLAYNAME: retval = UA_Variant_setScalarCopy(&v->value, &node->displayName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_DESCRIPTION: retval = UA_Variant_setScalarCopy(&v->value, &node->description, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_WRITEMASK: retval = UA_Variant_setScalarCopy(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]); break; case UA_ATTRIBUTEID_USERWRITEMASK: { UA_UInt32 userWriteMask = getUserWriteMask(server, session, node); retval = UA_Variant_setScalarCopy(&v->value, &userWriteMask, &UA_TYPES[UA_TYPES_UINT32]); break; } case UA_ATTRIBUTEID_ISABSTRACT: retval = readIsAbstractAttribute(node, &v->value); break; case UA_ATTRIBUTEID_SYMMETRIC: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode*)node)->symmetric, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_INVERSENAME: CHECK_NODECLASS(UA_NODECLASS_REFERENCETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ReferenceTypeNode*)node)->inverseName, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); break; case UA_ATTRIBUTEID_CONTAINSNOLOOPS: CHECK_NODECLASS(UA_NODECLASS_VIEW); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode*)node)->containsNoLoops, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EVENTNOTIFIER: CHECK_NODECLASS(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_ViewNode*)node)->eventNotifier, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_VALUE: { CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); /* VariableTypes don't have the AccessLevel concept. Always allow reading the value. */ if(node->nodeClass == UA_NODECLASS_VARIABLE) { /* The access to a value variable is granted via the AccessLevel * and UserAccessLevel attributes */ UA_Byte accessLevel = getAccessLevel(server, session, (const UA_VariableNode*)node); if(!(accessLevel & (UA_ACCESSLEVELMASK_READ))) { retval = UA_STATUSCODE_BADNOTREADABLE; break; } accessLevel = getUserAccessLevel(server, session, (const UA_VariableNode*)node); if(!(accessLevel & (UA_ACCESSLEVELMASK_READ))) { retval = UA_STATUSCODE_BADUSERACCESSDENIED; break; } } retval = readValueAttributeComplete(server, session, (const UA_VariableNode*)node, op_timestampsToReturn, &id->indexRange, v); break; } case UA_ATTRIBUTEID_DATATYPE: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableTypeNode*)node)->dataType, &UA_TYPES[UA_TYPES_NODEID]); break; case UA_ATTRIBUTEID_VALUERANK: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableTypeNode*)node)->valueRank, &UA_TYPES[UA_TYPES_INT32]); break; case UA_ATTRIBUTEID_ARRAYDIMENSIONS: CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE); retval = readArrayDimensionsAttribute((const UA_VariableNode*)node, v); break; case UA_ATTRIBUTEID_ACCESSLEVEL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->accessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; case UA_ATTRIBUTEID_USERACCESSLEVEL: { CHECK_NODECLASS(UA_NODECLASS_VARIABLE); UA_Byte userAccessLevel = getUserAccessLevel(server, session, (const UA_VariableNode*)node); retval = UA_Variant_setScalarCopy(&v->value, &userAccessLevel, &UA_TYPES[UA_TYPES_BYTE]); break; } case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->minimumSamplingInterval, &UA_TYPES[UA_TYPES_DOUBLE]); break; case UA_ATTRIBUTEID_HISTORIZING: CHECK_NODECLASS(UA_NODECLASS_VARIABLE); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_VariableNode*)node)->historizing, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_EXECUTABLE: CHECK_NODECLASS(UA_NODECLASS_METHOD); retval = UA_Variant_setScalarCopy(&v->value, &((const UA_MethodNode*)node)->executable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; case UA_ATTRIBUTEID_USEREXECUTABLE: { CHECK_NODECLASS(UA_NODECLASS_METHOD); UA_Boolean userExecutable = getUserExecutable(server, session, (const UA_MethodNode*)node); retval = UA_Variant_setScalarCopy(&v->value, &userExecutable, &UA_TYPES[UA_TYPES_BOOLEAN]); break; } default: retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID; } /* Release nodes */ UA_Nodestore_release(server, node); /* Return error code when reading has failed */ if(retval != UA_STATUSCODE_GOOD) { v->hasStatus = true; v->status = retval; return; } v->hasValue = true; /* Create server timestamp */ if(op_timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER || op_timestampsToReturn == UA_TIMESTAMPSTORETURN_BOTH) { v->serverTimestamp = UA_DateTime_now(); v->hasServerTimestamp = true; } /* Handle source time stamp */ if(id->attributeId == UA_ATTRIBUTEID_VALUE) { if(op_timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER || op_timestampsToReturn == UA_TIMESTAMPSTORETURN_NEITHER) { v->hasSourceTimestamp = false; v->hasSourcePicoseconds = false; } else if(!v->hasSourceTimestamp) { v->sourceTimestamp = UA_DateTime_now(); v->hasSourceTimestamp = true; } } }