static UA_StatusCode Service_Write_single_ValueDataSource(UA_Server *server, UA_Session *session, const UA_VariableNode *node, const UA_WriteValue *wvalue) { UA_assert(wvalue->attributeId == UA_ATTRIBUTEID_VALUE); UA_assert(node->nodeClass == UA_NODECLASS_VARIABLE || node->nodeClass == UA_NODECLASS_VARIABLETYPE); UA_assert(node->valueSource == UA_VALUESOURCE_DATASOURCE); if(node->value.dataSource.write == NULL) return UA_STATUSCODE_BADWRITENOTSUPPORTED; UA_StatusCode retval; if(wvalue->indexRange.length <= 0) { retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId, &wvalue->value.value, NULL); } else { UA_NumericRange range; retval = parse_numericrange(&wvalue->indexRange, &range); if(retval != UA_STATUSCODE_GOOD) return retval; retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId, &wvalue->value.value, &range); UA_free(range.dimensions); } return retval; }
static UA_StatusCode CopyValueIntoNode(UA_VariableNode *node, const UA_WriteValue *wvalue) { UA_assert(wvalue->attributeId == UA_ATTRIBUTEID_VALUE); UA_assert(node->nodeClass == UA_NODECLASS_VARIABLE || node->nodeClass == UA_NODECLASS_VARIABLETYPE); UA_assert(node->valueSource == UA_VALUESOURCE_VARIANT); /* Parse the range */ UA_NumericRange range; UA_NumericRange *rangeptr = NULL; UA_StatusCode retval = UA_STATUSCODE_GOOD; if(wvalue->indexRange.length > 0) { retval = parse_numericrange(&wvalue->indexRange, &range); if(retval != UA_STATUSCODE_GOOD) return retval; rangeptr = ⦥ } /* The nodeid on the wire may be != the nodeid in the node: opaque types, enums and bytestrings. nodeV contains the correct type definition. */ const UA_Variant *newV = &wvalue->value.value; UA_Variant *oldV = &node->value.variant.value; UA_Variant cast_v; if (oldV->type != NULL) { // Don't run NodeId_equal on a NULL pointer (happens if the variable never held a variant) if(!UA_NodeId_equal(&oldV->type->typeId, &newV->type->typeId)) { cast_v = wvalue->value.value; newV = &cast_v; enum type_equivalence te1 = typeEquivalence(oldV->type); enum type_equivalence te2 = typeEquivalence(newV->type); if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) { /* An enum was sent as an int32, or an opaque type as a bytestring. This is detected with the typeIndex indicated the "true" datatype. */ cast_v.type = oldV->type; } else if(oldV->type == &UA_TYPES[UA_TYPES_BYTE] && !UA_Variant_isScalar(oldV) && newV->type == &UA_TYPES[UA_TYPES_BYTESTRING] && UA_Variant_isScalar(newV)) { /* a string is written to a byte array */ UA_ByteString *str = (UA_ByteString*) newV->data; cast_v.arrayLength = str->length; cast_v.data = str->data; cast_v.type = &UA_TYPES[UA_TYPES_BYTE]; } else { if(rangeptr) UA_free(range.dimensions); return UA_STATUSCODE_BADTYPEMISMATCH; } } } if(!rangeptr) { UA_Variant_deleteMembers(&node->value.variant.value); UA_Variant_copy(newV, &node->value.variant.value); } else retval = UA_Variant_setRangeCopy(&node->value.variant.value, newV->data, newV->arrayLength, range); if(node->value.variant.callback.onWrite) node->value.variant.callback.onWrite(node->value.variant.callback.handle, node->nodeId, &node->value.variant.value, rangeptr); if(rangeptr) UA_free(range.dimensions); return retval; }
static void UA_NodeMap_releaseNode(void *context, const UA_Node *node) { if (!node) return; #ifdef UA_ENABLE_MULTITHREADING UA_NodeMap *ns = (UA_NodeMap*)context; #endif BEGIN_CRITSECT(ns); UA_NodeMapEntry *entry = container_of(node, UA_NodeMapEntry, node); UA_assert(&entry->node == node); UA_assert(entry->refCount > 0); --entry->refCount; cleanupEntry(entry); END_CRITSECT(ns); }
/** Copies the node into the entry. Then free the original node (but not its content). */ static void fillEntry(UA_NodeStoreEntry *entry, UA_Node *node) { size_t nodesize = 0; switch(node->nodeClass) { case UA_NODECLASS_OBJECT: nodesize = sizeof(UA_ObjectNode); break; case UA_NODECLASS_VARIABLE: nodesize = sizeof(UA_VariableNode); break; case UA_NODECLASS_METHOD: nodesize = sizeof(UA_MethodNode); break; case UA_NODECLASS_OBJECTTYPE: nodesize = sizeof(UA_ObjectTypeNode); break; case UA_NODECLASS_VARIABLETYPE: nodesize = sizeof(UA_VariableTypeNode); break; case UA_NODECLASS_REFERENCETYPE: nodesize = sizeof(UA_ReferenceTypeNode); break; case UA_NODECLASS_DATATYPE: nodesize = sizeof(UA_DataTypeNode); break; case UA_NODECLASS_VIEW: nodesize = sizeof(UA_ViewNode); break; default: UA_assert(UA_FALSE); } memcpy(&entry->node, node, nodesize); UA_free(node); entry->taken = UA_TRUE; }
static void node_deleteMembers(UA_Node *node) { switch(node->nodeClass) { case UA_NODECLASS_OBJECT: UA_ObjectNode_deleteMembers((UA_ObjectNode *)node); break; case UA_NODECLASS_VARIABLE: UA_VariableNode_deleteMembers((UA_VariableNode *)node); break; case UA_NODECLASS_METHOD: UA_MethodNode_deleteMembers((UA_MethodNode *)node); break; case UA_NODECLASS_OBJECTTYPE: UA_ObjectTypeNode_deleteMembers((UA_ObjectTypeNode *)node); break; case UA_NODECLASS_VARIABLETYPE: UA_VariableTypeNode_deleteMembers((UA_VariableTypeNode *)node); break; case UA_NODECLASS_REFERENCETYPE: UA_ReferenceTypeNode_deleteMembers((UA_ReferenceTypeNode *)node); break; case UA_NODECLASS_DATATYPE: UA_DataTypeNode_deleteMembers((UA_DataTypeNode *)node); break; case UA_NODECLASS_VIEW: UA_ViewNode_deleteMembers((UA_ViewNode *)node); break; default: UA_assert(UA_FALSE); break; } }
/* The occupancy of the table after the call will be about 50% */ static UA_StatusCode expand(UA_NodeMap *ns) { UA_UInt32 osize = ns->size; UA_UInt32 count = ns->count; /* Resize only when table after removal of unused elements is either too full or too empty */ if(count * 2 < osize && (count * 8 > osize || osize <= UA_NODEMAP_MINSIZE)) return UA_STATUSCODE_GOOD; UA_NodeMapEntry **oentries = ns->entries; UA_UInt32 nindex = higher_prime_index(count * 2); UA_UInt32 nsize = primes[nindex]; UA_NodeMapEntry **nentries = (UA_NodeMapEntry **)UA_calloc(nsize, sizeof(UA_NodeMapEntry*)); if(!nentries) return UA_STATUSCODE_BADOUTOFMEMORY; ns->entries = nentries; ns->size = nsize; ns->sizePrimeIndex = nindex; /* recompute the position of every entry and insert the pointer */ for(size_t i = 0, j = 0; i < osize && j < count; ++i) { if(oentries[i] <= UA_NODEMAP_TOMBSTONE) continue; UA_NodeMapEntry **e = findFreeSlot(ns, &oentries[i]->node.nodeId); UA_assert(e); *e = oentries[i]; ++j; } UA_free(oentries); return UA_STATUSCODE_GOOD; }
static UA_StatusCode writeDataTypeAttribute(UA_Server *server, UA_Session *session, UA_VariableNode *node, const UA_VariableTypeNode *type, const UA_NodeId *dataType) { UA_assert(node != NULL); UA_assert(type != NULL); /* If this is a variabletype, there must be no instances or subtypes of it when we do the change */ if(node->nodeClass == UA_NODECLASS_VARIABLETYPE && UA_Node_hasSubTypeOrInstances((const UA_Node*)node)) return UA_STATUSCODE_BADINTERNALERROR; /* Does the new type match the constraints of the variabletype? */ if(!compatibleDataType(server, dataType, &type->dataType)) return UA_STATUSCODE_BADTYPEMISMATCH; /* Check if the current value would match the new type */ UA_DataValue value; UA_DataValue_init(&value); UA_StatusCode retval = readValueAttribute(server, session, node, &value); if(retval != UA_STATUSCODE_GOOD) return retval; if(value.hasValue) { if(!compatibleValue(server, dataType, node->valueRank, node->arrayDimensionsSize, node->arrayDimensions, &value.value, NULL)) retval = UA_STATUSCODE_BADTYPEMISMATCH; UA_DataValue_deleteMembers(&value); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER, "The current value does not match the new data type"); return retval; } } /* Replace the datatype nodeid */ UA_NodeId dtCopy = node->dataType; retval = UA_NodeId_copy(dataType, &node->dataType); if(retval != UA_STATUSCODE_GOOD) { node->dataType = dtCopy; return retval; } UA_NodeId_deleteMembers(&dtCopy); return UA_STATUSCODE_GOOD; }
/* Stack layout: ... | node | type */ static UA_StatusCode writeValueRankAttribute(UA_Server *server, UA_Session *session, UA_VariableNode *node, const UA_VariableTypeNode *type, UA_Int32 valueRank) { UA_assert(node != NULL); UA_assert(type != NULL); UA_Int32 constraintValueRank = type->valueRank; /* If this is a variabletype, there must be no instances or subtypes of it * when we do the change */ if(node->nodeClass == UA_NODECLASS_VARIABLETYPE && UA_Node_hasSubTypeOrInstances((const UA_Node*)node)) return UA_STATUSCODE_BADINTERNALERROR; /* Check if the valuerank of the variabletype allows the change. */ if(!compatibleValueRanks(valueRank, constraintValueRank)) return UA_STATUSCODE_BADTYPEMISMATCH; /* Check if the new valuerank is compatible with the array dimensions. Use * the read service to handle data sources. */ size_t arrayDims = node->arrayDimensionsSize; if(arrayDims == 0) { /* the value could be an array with no arrayDimensions defined. dimensions zero indicate a scalar for compatibleValueRankArrayDimensions. */ UA_DataValue value; UA_DataValue_init(&value); UA_StatusCode retval = readValueAttribute(server, session, node, &value); if(retval != UA_STATUSCODE_GOOD) return retval; if(!value.hasValue || !value.value.type) { /* no value -> apply */ node->valueRank = valueRank; return UA_STATUSCODE_GOOD; } if(!UA_Variant_isScalar(&value.value)) arrayDims = 1; UA_DataValue_deleteMembers(&value); } if(!compatibleValueRankArrayDimensions(valueRank, arrayDims)) return UA_STATUSCODE_BADTYPEMISMATCH; /* All good, apply the change */ node->valueRank = valueRank; return UA_STATUSCODE_GOOD; }
static void addNodeFromAttributes(UA_Server *server, UA_Session *session, UA_AddNodesItem *item, UA_AddNodesResult *result) { // adding nodes to ns0 is not allowed over the wire if(item->requestedNewNodeId.nodeId.namespaceIndex == 0) { result->statusCode = UA_STATUSCODE_BADNODEIDREJECTED; return; } // parse the node UA_Node *node = UA_NULL; switch (item->nodeClass) { case UA_NODECLASS_OBJECT: result->statusCode = parseObjectNode(&item->nodeAttributes, &node); break; case UA_NODECLASS_OBJECTTYPE: result->statusCode = parseObjectTypeNode(&item->nodeAttributes, &node); break; case UA_NODECLASS_REFERENCETYPE: result->statusCode = parseReferenceTypeNode(&item->nodeAttributes, &node); break; case UA_NODECLASS_VARIABLE: result->statusCode = parseVariableNode(&item->nodeAttributes, &node); break; default: result->statusCode = UA_STATUSCODE_BADNOTIMPLEMENTED; } if(result->statusCode != UA_STATUSCODE_GOOD) return; // The BrowseName was not included with the NodeAttribute ExtensionObject UA_QualifiedName_init(&(node->browseName)); UA_QualifiedName_copy(&(item->browseName), &(node->browseName)); UA_NodeId_copy(&item->requestedNewNodeId.nodeId, &node->nodeId); // add the node *result = UA_Server_addNodeWithSession(server, session, node, item->parentNodeId, item->referenceTypeId); if(result->statusCode != UA_STATUSCODE_GOOD) { switch (node->nodeClass) { case UA_NODECLASS_OBJECT: UA_ObjectNode_delete((UA_ObjectNode*)node); break; case UA_NODECLASS_OBJECTTYPE: UA_ObjectTypeNode_delete((UA_ObjectTypeNode*)node); break; case UA_NODECLASS_REFERENCETYPE: UA_ReferenceTypeNode_delete((UA_ReferenceTypeNode*)node); break; case UA_NODECLASS_VARIABLE: UA_VariableNode_delete((UA_VariableNode*)node); break; default: UA_assert(UA_FALSE); } } }
static void UA_NodeMap_deleteNode(void *context, UA_Node *node) { #ifdef UA_ENABLE_MULTITHREADING UA_NodeMap *ns = (UA_NodeMap*)context; #endif BEGIN_CRITSECT(ns); UA_NodeMapEntry *entry = container_of(node, UA_NodeMapEntry, node); UA_assert(&entry->node == node); deleteEntry(entry); END_CRITSECT(ns); }
size_t UA_readNumberWithBase(UA_Byte *buf, size_t buflen, UA_UInt32 *number, UA_Byte base) { UA_assert(buf); UA_assert(number); u32 n = 0; size_t progress = 0; /* read numbers until the end or a non-number character appears */ while(progress < buflen) { u8 c = buf[progress]; if(c >= '0' && c <= '9' && c <= '0' + (base-1)) n = (n * base) + c - '0'; else if(base > 9 && c >= 'a' && c <= 'z' && c <= 'a' + (base-11)) n = (n * base) + c-'a' + 10; else if(base > 9 && c >= 'A' && c <= 'Z' && c <= 'A' + (base-11)) n = (n * base) + c-'A' + 10; else break; ++progress; } *number = n; return progress; }
void Service_Write(UA_Server *server, UA_Session *session, const UA_WriteRequest *request, UA_WriteResponse *response) { UA_assert(server != UA_NULL && session != UA_NULL && request != UA_NULL && response != UA_NULL); UA_StatusCode retval = UA_Array_new((void**)&response->results, request->nodesToWriteSize, &UA_TYPES[UA_STATUSCODE]); if(retval) { response->responseHeader.serviceResult = retval; return; } response->resultsSize = request->nodesToWriteSize; for(UA_Int32 i = 0;i < request->nodesToWriteSize;i++) response->results[i] = Service_Write_writeNode(server, &request->nodesToWrite[i]); }
void Service_Write(UA_Server *server, UA_Session *session, const UA_WriteRequest *request, UA_WriteResponse *response) { UA_assert(server != UA_NULL && session != UA_NULL && request != UA_NULL && response != UA_NULL); if(request->nodesToWriteSize <= 0){ response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO; return; } response->results = UA_Array_new(&UA_TYPES[UA_TYPES_STATUSCODE], request->nodesToWriteSize); if(!response->results) { response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; return; } #ifdef UA_EXTERNAL_NAMESPACES #ifdef NO_ALLOCA UA_Boolean isExternal[request->nodesToWriteSize]; UA_UInt32 indices[request->nodesToWriteSize]; #else UA_Boolean *isExternal = UA_alloca(sizeof(UA_Boolean) * request->nodesToWriteSize); UA_UInt32 *indices = UA_alloca(sizeof(UA_UInt32) * request->nodesToWriteSize); #endif /*NO_ALLOCA */ UA_memset(isExternal, UA_FALSE, sizeof(UA_Boolean)*request->nodesToWriteSize); for(size_t j = 0; j < server->externalNamespacesSize; j++) { UA_UInt32 indexSize = 0; for(UA_Int32 i = 0; i < request->nodesToWriteSize; i++) { if(request->nodesToWrite[i].nodeId.namespaceIndex != server->externalNamespaces[j].index) continue; isExternal[i] = UA_TRUE; indices[indexSize] = i; indexSize++; } if(indexSize == 0) continue; UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore; ens->writeNodes(ens->ensHandle, &request->requestHeader, request->nodesToWrite, indices, indexSize, response->results, response->diagnosticInfos); } #endif response->resultsSize = request->nodesToWriteSize; for(UA_Int32 i = 0;i < request->nodesToWriteSize;i++) { #ifdef UA_EXTERNAL_NAMESPACES if(!isExternal[i]) #endif response->results[i] = writeValue(server, &request->nodesToWrite[i]); } }
void Service_Write(UA_Server *server, UA_Session *session, const UA_WriteRequest *request, UA_WriteResponse *response) { UA_assert(server != NULL && session != NULL && request != NULL && response != NULL); UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SESSION, "Processing WriteRequest for Session (ns=%i,i=%i)", session->sessionId.namespaceIndex, session->sessionId.identifier.numeric); if(request->nodesToWriteSize <= 0) { response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO; return; } response->results = UA_Array_new(request->nodesToWriteSize, &UA_TYPES[UA_TYPES_STATUSCODE]); if(!response->results) { response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; return; } #ifdef UA_ENABLE_EXTERNAL_NAMESPACES UA_Boolean isExternal[request->nodesToWriteSize]; UA_UInt32 indices[request->nodesToWriteSize]; memset(isExternal, UA_FALSE, sizeof(UA_Boolean)*request->nodesToWriteSize); for(size_t j = 0; j < server->externalNamespacesSize; j++) { UA_UInt32 indexSize = 0; for(UA_Int32 i = 0; i < request->nodesToWriteSize; i++) { if(request->nodesToWrite[i].nodeId.namespaceIndex != server->externalNamespaces[j].index) continue; isExternal[i] = UA_TRUE; indices[indexSize] = i; indexSize++; } if(indexSize == 0) continue; UA_ExternalNodeStore *ens = &server->externalNamespaces[j].externalNodeStore; ens->writeNodes(ens->ensHandle, &request->requestHeader, request->nodesToWrite, indices, indexSize, response->results, response->diagnosticInfos); } #endif response->resultsSize = request->nodesToWriteSize; for(size_t i = 0;i < request->nodesToWriteSize;i++) { #ifdef UA_ENABLE_EXTERNAL_NAMESPACES if(!isExternal[i]) #endif response->results[i] = Service_Write_single(server, session, &request->nodesToWrite[i]); } }
const UA_Node * getNodeType(UA_Server *server, const UA_Node *node) { /* The reference to the parent is different for variable and variabletype */ UA_NodeId parentRef; UA_Boolean inverse; UA_NodeClass typeNodeClass; switch(node->nodeClass) { case UA_NODECLASS_OBJECT: parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION); inverse = false; typeNodeClass = UA_NODECLASS_OBJECTTYPE; break; case UA_NODECLASS_VARIABLE: parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION); inverse = false; typeNodeClass = UA_NODECLASS_VARIABLETYPE; break; case UA_NODECLASS_OBJECTTYPE: case UA_NODECLASS_VARIABLETYPE: case UA_NODECLASS_REFERENCETYPE: case UA_NODECLASS_DATATYPE: parentRef = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE); inverse = true; typeNodeClass = node->nodeClass; break; default: return NULL; } /* Return the first matching candidate */ for(size_t i = 0; i < node->referencesSize; ++i) { if(node->references[i].isInverse != inverse) continue; if(!UA_NodeId_equal(&node->references[i].referenceTypeId, &parentRef)) continue; UA_assert(node->references[i].targetIdsSize > 0); const UA_NodeId *targetId = &node->references[i].targetIds[0].nodeId; const UA_Node *type = UA_Nodestore_getNode(server->nsCtx, targetId); if(!type) continue; if(type->nodeClass == typeNodeClass) return type; UA_Nodestore_releaseNode(server->nsCtx, type); } return NULL; }
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); }
static void UA_NodeMap_delete(void *context) { UA_NodeMap *ns = (UA_NodeMap*)context; #ifdef UA_ENABLE_MULTITHREADING pthread_mutex_destroy(&ns->mutex); #endif UA_UInt32 size = ns->size; UA_NodeMapEntry **entries = ns->entries; for(UA_UInt32 i = 0; i < size; ++i) { if(entries[i] > UA_NODEMAP_TOMBSTONE) { /* On debugging builds, check that all nodes were release */ UA_assert(entries[i]->refCount == 0); /* Delete the node */ deleteEntry(entries[i]); } } UA_free(ns->entries); UA_free(ns); }
static void processMSG(UA_Server *server, UA_SecureChannel *channel, UA_UInt32 requestId, const UA_ByteString *msg) { /* At 0, the nodeid starts... */ size_t ppos = 0; size_t *offset = &ppos; /* Decode the nodeid */ UA_NodeId requestTypeId; UA_StatusCode retval = UA_NodeId_decodeBinary(msg, offset, &requestTypeId); if(retval != UA_STATUSCODE_GOOD) return; if(requestTypeId.identifierType != UA_NODEIDTYPE_NUMERIC) UA_NodeId_deleteMembers(&requestTypeId); /* leads to badserviceunsupported */ /* Store the start-position of the request */ size_t requestPos = *offset; /* Get the service pointers */ UA_Service service = NULL; const UA_DataType *requestType = NULL; const UA_DataType *responseType = NULL; UA_Boolean sessionRequired = true; getServicePointers(requestTypeId.identifier.numeric, &requestType, &responseType, &service, &sessionRequired); if(!requestType) { if(requestTypeId.identifier.numeric == 787) { UA_LOG_INFO_CHANNEL(server->config.logger, channel, "Client requested a subscription, " \ "but those are not enabled in the build"); } else { UA_LOG_INFO_CHANNEL(server->config.logger, channel, "Unknown request %i", requestTypeId.identifier.numeric); } sendError(channel, msg, requestPos, &UA_TYPES[UA_TYPES_SERVICEFAULT], requestId, UA_STATUSCODE_BADSERVICEUNSUPPORTED); return; } UA_assert(responseType); #ifdef UA_ENABLE_NONSTANDARD_STATELESS /* Stateless extension: Sessions are optional */ sessionRequired = false; #endif /* Decode the request */ void *request = UA_alloca(requestType->memSize); UA_RequestHeader *requestHeader = (UA_RequestHeader*)request; retval = UA_decodeBinary(msg, offset, request, requestType); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_DEBUG_CHANNEL(server->config.logger, channel, "Could not decode the request"); sendError(channel, msg, requestPos, responseType, requestId, retval); return; } /* Prepare the respone */ void *response = UA_alloca(responseType->memSize); UA_init(response, responseType); UA_Session *session = NULL; /* must be initialized before goto send_response */ /* CreateSession doesn't need a session */ if(requestType == &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST]) { Service_CreateSession(server, channel, request, response); goto send_response; } /* Find the matching session */ session = UA_SecureChannel_getSession(channel, &requestHeader->authenticationToken); if(!session) session = UA_SessionManager_getSession(&server->sessionManager, &requestHeader->authenticationToken); if(requestType == &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST]) { if(!session) { UA_LOG_DEBUG_CHANNEL(server->config.logger, channel, "Trying to activate a session that is " \ "not known in the server"); sendError(channel, msg, requestPos, responseType, requestId, UA_STATUSCODE_BADSESSIONIDINVALID); UA_deleteMembers(request, requestType); return; } Service_ActivateSession(server, channel, session, request, response); goto send_response; } /* Set an anonymous, inactive session for services that need no session */ UA_Session anonymousSession; if(!session) { if(sessionRequired) { UA_LOG_INFO_CHANNEL(server->config.logger, channel, "Service request %i without a valid session", requestType->binaryEncodingId); sendError(channel, msg, requestPos, responseType, requestId, UA_STATUSCODE_BADSESSIONIDINVALID); UA_deleteMembers(request, requestType); return; } UA_Session_init(&anonymousSession); anonymousSession.sessionId = UA_NODEID_GUID(0, UA_GUID_NULL); anonymousSession.channel = channel; session = &anonymousSession; } /* Trying to use a non-activated session? */ if(sessionRequired && !session->activated) { UA_LOG_INFO_SESSION(server->config.logger, session, "Calling service %i on a non-activated session", requestType->binaryEncodingId); sendError(channel, msg, requestPos, responseType, requestId, UA_STATUSCODE_BADSESSIONNOTACTIVATED); UA_SessionManager_removeSession(&server->sessionManager, &session->authenticationToken); UA_deleteMembers(request, requestType); return; } /* The session is bound to another channel */ if(session->channel != channel) { UA_LOG_DEBUG_CHANNEL(server->config.logger, channel, "Client tries to use an obsolete securechannel"); sendError(channel, msg, requestPos, responseType, requestId, UA_STATUSCODE_BADSECURECHANNELIDINVALID); UA_deleteMembers(request, requestType); return; } /* Update the session lifetime */ UA_Session_updateLifetime(session); #ifdef UA_ENABLE_SUBSCRIPTIONS /* The publish request is not answered immediately */ if(requestType == &UA_TYPES[UA_TYPES_PUBLISHREQUEST]) { Service_Publish(server, session, request, requestId); UA_deleteMembers(request, requestType); return; } #endif /* Call the service */ UA_assert(service); /* For all services besides publish, the service pointer is non-NULL*/ service(server, session, request, response); send_response: /* Send the response */ ((UA_ResponseHeader*)response)->requestHandle = requestHeader->requestHandle; ((UA_ResponseHeader*)response)->timestamp = UA_DateTime_now(); retval = UA_SecureChannel_sendBinaryMessage(channel, requestId, response, responseType); if(retval != UA_STATUSCODE_GOOD) UA_LOG_INFO_CHANNEL(server->config.logger, channel, "Could not send the message over " "the SecureChannel with error code 0x%08x", retval); /* Clean up */ UA_deleteMembers(request, requestType); UA_deleteMembers(response, responseType); }
/* Stack layout: ... | node | type */ static UA_StatusCode writeArrayDimensionsAttribute(UA_Server *server, UA_Session *session, UA_VariableNode *node, const UA_VariableTypeNode *type, size_t arrayDimensionsSize, UA_UInt32 *arrayDimensions) { UA_assert(node != NULL); UA_assert(type != NULL); /* If this is a variabletype, there must be no instances or subtypes of it * when we do the change */ if(node->nodeClass == UA_NODECLASS_VARIABLETYPE && UA_Node_hasSubTypeOrInstances((UA_Node*)node)) { UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER, "Cannot change a variable type with existing instances"); return UA_STATUSCODE_BADINTERNALERROR; } /* Check that the array dimensions match with the valuerank */ if(!compatibleValueRankArrayDimensions(node->valueRank, arrayDimensionsSize)) { UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER, "The current value rank does not match the new array dimensions"); return UA_STATUSCODE_BADTYPEMISMATCH; } /* Check if the array dimensions match with the wildcards in the * variabletype (dimension length 0) */ if(type->arrayDimensions && !compatibleArrayDimensions(type->arrayDimensionsSize, type->arrayDimensions, arrayDimensionsSize, arrayDimensions)) { UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER, "Array dimensions in the variable type do not match"); return UA_STATUSCODE_BADTYPEMISMATCH; } /* Check if the current value is compatible with the array dimensions */ UA_DataValue value; UA_DataValue_init(&value); UA_StatusCode retval = readValueAttribute(server, session, node, &value); if(retval != UA_STATUSCODE_GOOD) return retval; if(value.hasValue) { if(!compatibleValueArrayDimensions(&value.value, arrayDimensionsSize, arrayDimensions)) retval = UA_STATUSCODE_BADTYPEMISMATCH; UA_DataValue_deleteMembers(&value); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER, "Array dimensions in the current value do not match"); return retval; } } /* Ok, apply */ UA_UInt32 *oldArrayDimensions = node->arrayDimensions; retval = UA_Array_copy(arrayDimensions, arrayDimensionsSize, (void**)&node->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]); if(retval != UA_STATUSCODE_GOOD) return retval; UA_free(oldArrayDimensions); node->arrayDimensionsSize = arrayDimensionsSize; return UA_STATUSCODE_GOOD; }
/* Stack layout: ... | node */ static UA_StatusCode writeValueAttribute(UA_Server *server, UA_Session *session, UA_VariableNode *node, const UA_DataValue *value, const UA_String *indexRange) { UA_assert(node != NULL); /* Parse the range */ UA_NumericRange range; UA_NumericRange *rangeptr = NULL; UA_StatusCode retval = UA_STATUSCODE_GOOD; if(indexRange && indexRange->length > 0) { retval = UA_NumericRange_parseFromString(&range, indexRange); if(retval != UA_STATUSCODE_GOOD) return retval; rangeptr = ⦥ } /* Created an editable version. The data is not touched. Only the variant * "container". */ UA_DataValue adjustedValue = *value; /* Type checking. May change the type of editableValue */ if(value->hasValue && value->value.type) { adjustValue(server, &adjustedValue.value, &node->dataType); /* The value may be an extension object, especially the nodeset compiler uses * extension objects to write variable values. * If value is an extension object we check if the current node value is also an extension object. */ UA_Boolean compatible; if (value->value.type->typeId.identifierType == UA_NODEIDTYPE_NUMERIC && value->value.type->typeId.identifier.numeric == UA_NS0ID_STRUCTURE) { const UA_NodeId nodeDataType = UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE); compatible = compatibleValue(server, &nodeDataType, node->valueRank, node->arrayDimensionsSize, node->arrayDimensions, &adjustedValue.value, rangeptr); } else { compatible = compatibleValue(server, &node->dataType, node->valueRank, node->arrayDimensionsSize, node->arrayDimensions, &adjustedValue.value, rangeptr); } if(!compatible) { if(rangeptr) UA_free(range.dimensions); return UA_STATUSCODE_BADTYPEMISMATCH; } } /* Set the source timestamp if there is none */ if(!adjustedValue.hasSourceTimestamp) { adjustedValue.sourceTimestamp = UA_DateTime_now(); adjustedValue.hasSourceTimestamp = true; } /* Ok, do it */ if(node->valueSource == UA_VALUESOURCE_DATA) { if(!rangeptr) retval = writeValueAttributeWithoutRange(node, &adjustedValue); else retval = writeValueAttributeWithRange(node, &adjustedValue, rangeptr); /* Callback after writing */ if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite) node->value.data.callback.onWrite(server, &session->sessionId, session->sessionHandle, &node->nodeId, node->context, rangeptr, &adjustedValue); } else { if(node->value.dataSource.write) { retval = node->value.dataSource.write(server, &session->sessionId, session->sessionHandle, &node->nodeId, node->context, rangeptr, &adjustedValue); } else { retval = UA_STATUSCODE_BADWRITENOTSUPPORTED; } } /* Clean up */ if(rangeptr) UA_free(range.dimensions); return retval; }
/* 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 UA_StatusCode setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon, UA_MonitoringMode monitoringMode, const UA_MonitoringParameters *params, const UA_DataType* dataType) { /* Filter */ if(params->filter.encoding != UA_EXTENSIONOBJECT_DECODED) { UA_DataChangeFilter_init(&(mon->filter.dataChangeFilter)); mon->filter.dataChangeFilter.trigger = UA_DATACHANGETRIGGER_STATUSVALUE; } else if(params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) { UA_DataChangeFilter *filter = (UA_DataChangeFilter *)params->filter.content.decoded.data; // TODO implement EURange to support UA_DEADBANDTYPE_PERCENT switch(filter->deadbandType) { case UA_DEADBANDTYPE_NONE: break; case UA_DEADBANDTYPE_ABSOLUTE: if(!dataType || !UA_DataType_isNumeric(dataType)) return UA_STATUSCODE_BADFILTERNOTALLOWED; break; case UA_DEADBANDTYPE_PERCENT: return UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED; default: return UA_STATUSCODE_BADMONITOREDITEMFILTERUNSUPPORTED; } UA_DataChangeFilter_copy(filter, &mon->filter.dataChangeFilter); #ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS } else if (params->filter.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTER]) { UA_EventFilter_copy((UA_EventFilter *)params->filter.content.decoded.data, &mon->filter.eventFilter); #endif } else { return UA_STATUSCODE_BADMONITOREDITEMFILTERINVALID; } /* <-- The point of no return --> */ /* Unregister the callback */ UA_MonitoredItem_unregisterSampleCallback(server, mon); /* Remove the old samples */ UA_ByteString_deleteMembers(&mon->lastSampledValue); UA_Variant_deleteMembers(&mon->lastValue); /* ClientHandle */ mon->clientHandle = params->clientHandle; /* SamplingInterval */ UA_Double samplingInterval = params->samplingInterval; if(mon->attributeId == UA_ATTRIBUTEID_VALUE) { mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY; const UA_VariableNode *vn = (const UA_VariableNode *) UA_Nodestore_get(server, &mon->monitoredNodeId); if(vn) { if(vn->nodeClass == UA_NODECLASS_VARIABLE && samplingInterval < vn->minimumSamplingInterval) samplingInterval = vn->minimumSamplingInterval; UA_Nodestore_release(server, (const UA_Node *)vn); } } else if(mon->attributeId == UA_ATTRIBUTEID_EVENTNOTIFIER) { /* TODO: events should not need a samplinginterval */ samplingInterval = 10000.0f; // 10 seconds to reduce the load mon->monitoredItemType = UA_MONITOREDITEMTYPE_EVENTNOTIFY; } else { mon->monitoredItemType = UA_MONITOREDITEMTYPE_CHANGENOTIFY; } mon->samplingInterval = samplingInterval; UA_BOUNDEDVALUE_SETWBOUNDS(server->config.samplingIntervalLimits, samplingInterval, mon->samplingInterval); if(samplingInterval != samplingInterval) /* Check for nan */ mon->samplingInterval = server->config.samplingIntervalLimits.min; UA_assert(mon->monitoredItemType != 0); /* QueueSize */ UA_BOUNDEDVALUE_SETWBOUNDS(server->config.queueSizeLimits, params->queueSize, mon->maxQueueSize); /* DiscardOldest */ mon->discardOldest = params->discardOldest; /* Register sample callback if reporting is enabled */ mon->monitoringMode = monitoringMode; if(monitoringMode == UA_MONITORINGMODE_REPORTING) return UA_MonitoredItem_registerSampleCallback(server, mon); return UA_STATUSCODE_GOOD; }
UA_StatusCode UA_parseEndpointUrl(const UA_String *endpointUrl, UA_String *outHostname, u16 *outPort, UA_String *outPath) { /* Url must begin with "opc.tcp://" or opc.udp:// (if pubsub enabled) */ if(endpointUrl->length < 11) { return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; } else if (strncmp((char*)endpointUrl->data, "opc.tcp://", 10) != 0) { #ifdef UA_ENABLE_PUBSUB if (strncmp((char*)endpointUrl->data, "opc.udp://", 10) != 0 && strncmp((char*)endpointUrl->data, "opc.mqtt://", 11) != 0) { return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; } #else return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; #endif } /* Where does the hostname end? */ size_t curr = 10; if(endpointUrl->data[curr] == '[') { /* IPv6: opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path */ for(; curr < endpointUrl->length; ++curr) { if(endpointUrl->data[curr] == ']') break; } if(curr == endpointUrl->length) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; curr++; } else { /* IPv4 or hostname: opc.tcp://something.something:1234/path */ for(; curr < endpointUrl->length; ++curr) { if(endpointUrl->data[curr] == ':' || endpointUrl->data[curr] == '/') break; } } /* Set the hostname */ outHostname->data = &endpointUrl->data[10]; outHostname->length = curr - 10; if(curr == endpointUrl->length) return UA_STATUSCODE_GOOD; /* Set the port */ if(endpointUrl->data[curr] == ':') { if(++curr == endpointUrl->length) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; u32 largeNum; size_t progress = UA_readNumber(&endpointUrl->data[curr], endpointUrl->length - curr, &largeNum); if(progress == 0 || largeNum > 65535) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; /* Test if the end of a valid port was reached */ curr += progress; if(curr == endpointUrl->length || endpointUrl->data[curr] == '/') *outPort = (u16)largeNum; if(curr == endpointUrl->length) return UA_STATUSCODE_GOOD; } /* Set the path */ UA_assert(curr < endpointUrl->length); if(endpointUrl->data[curr] != '/') return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; if(++curr == endpointUrl->length) return UA_STATUSCODE_GOOD; outPath->data = &endpointUrl->data[curr]; outPath->length = endpointUrl->length - curr; /* Remove trailing slash from the path */ if(endpointUrl->data[endpointUrl->length - 1] == '/') outPath->length--; return UA_STATUSCODE_GOOD; }