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 = &range;
    }

    /* 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);
}
Beispiel #4
0
/** 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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
/* 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);
}
Beispiel #11
0
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]);
    }
}
Beispiel #15
0
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);
}
Beispiel #18
0
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);
}
Beispiel #19
0
/* 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;
}
Beispiel #20
0
/* 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 = &range;
    }

    /* 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;
}
Beispiel #23
0
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;
}