Browse Source

Merge branch '0.2'

Julius Pfrommer 8 years ago
parent
commit
137c05cf90

+ 25 - 0
include/ua_connection.h

@@ -117,6 +117,31 @@ struct UA_Connection {
 void UA_EXPORT UA_Connection_init(UA_Connection *connection);
 void UA_EXPORT UA_Connection_deleteMembers(UA_Connection *connection);
 
+
+/**
+ * EndpointURL helper
+ */
+
+/**
+ * Split the given endpoint url into hostname and port
+ * @param endpointUrl The endpoint URL to split up
+ * @param hostname the target array for hostname. Has to be at least 256 size. If an IPv6 address is given, hostname contains e.g. '[2001:0db8:85a3::8a2e:0370:7334]'
+ * @param port set to the port of the url or 0
+ * @param path pointing to the end of given endpointUrl or to NULL if no path given. The starting '/' is NOT included in path
+ * @return UA_STATUSCODE_BADOUTOFRANGE if url too long, UA_STATUSCODE_BADATTRIBUTEIDINVALID if url not starting with 'opc.tcp://', UA_STATUSCODE_GOOD on success
+ */
+UA_StatusCode UA_EXPORT UA_EndpointUrl_split(const char *endpointUrl, char *hostname, UA_UInt16 * port, const char ** path);
+
+/**
+ * Convert given byte string to a number. Returns the number of valid digits.
+ * Stops if a non-digit char is found and returns the number of digits up to that point.
+ * @param buf
+ * @param buflen
+ * @param number
+ * @return
+ */
+size_t UA_EXPORT UA_readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 7 - 4
include/ua_types.h

@@ -209,12 +209,15 @@ void UA_EXPORT UA_Array_delete(void *p, size_t size, const UA_DataType *type);
  * wire, it only exists as an encoded string, such as "1:2,0:3,5". The colon
  * separates min/max index and the comma separates dimensions. A single value
  * indicates a range with a single element (min==max). */
+
+typedef struct {
+    UA_UInt32 min;
+    UA_UInt32 max;
+} UA_NumericRangeDimension;
+    
 typedef struct {
     size_t dimensionsSize;
-    struct UA_NumericRangeDimension {
-        UA_UInt32 min;
-        UA_UInt32 max;
-    } *dimensions;
+    UA_NumericRangeDimension *dimensions;
 } UA_NumericRange;
 
 /**

+ 26 - 24
plugins/ua_network_tcp.c

@@ -594,40 +594,42 @@ UA_ClientConnectionTCP(UA_ConnectionConfig localConf, const char *endpointUrl, U
     connection.releaseSendBuffer = ClientNetworkLayerReleaseBuffer;
     connection.releaseRecvBuffer = ClientNetworkLayerReleaseBuffer;
 
-    size_t urlLength = strlen(endpointUrl);
-    if(urlLength < 11 || urlLength >= 512) {
-        UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK, "Server url size invalid");
-        return connection;
-    }
-    if(strncmp(endpointUrl, "opc.tcp://", 10) != 0) {
-        UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK, "Server url does not begin with opc.tcp://");
+    char hostname[512];
+    UA_UInt16 port = 0;
+    const char *path = NULL;
+
+    UA_StatusCode parse_retval = UA_EndpointUrl_split(endpointUrl, hostname, &port, &path);
+    if(parse_retval != UA_STATUSCODE_GOOD) {
+        if(parse_retval == UA_STATUSCODE_BADOUTOFRANGE)
+            UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                           "Server url is invalid: %s", endpointUrl);
+        else if(parse_retval == UA_STATUSCODE_BADATTRIBUTEIDINVALID)
+            UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                           "Server url does not begin with 'opc.tcp://'  '%s'", endpointUrl);
         return connection;
     }
 
-    /* where does the port begin? */
-    size_t portpos = 10;
-    for(; portpos < urlLength-1; portpos++) {
-        if(endpointUrl[portpos] == ':')
-            break;
+    if(port == 0) {
+        port = 4840;
+        UA_LOG_INFO(logger, UA_LOGCATEGORY_NETWORK,
+                    "No port defined, using standard port %d", port);
     }
 
-    char hostname[512];
-    memcpy(hostname, &endpointUrl[10], portpos - 10);
-    hostname[portpos-10] = 0;
-
-    const char *port = "4840";
-    if(portpos < urlLength - 1)
-        port = &endpointUrl[portpos + 1];
-    else
-        UA_LOG_INFO(logger, UA_LOGCATEGORY_NETWORK, "No port defined, using standard port %s", port);
-
     struct addrinfo hints, *server;
     memset(&hints, 0, sizeof(hints));
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_family = AF_INET;
-    int error = getaddrinfo(hostname, port, &hints, &server);
+    char portStr[6];
+    #ifndef _MSC_VER
+    snprintf(portStr, 6, "%d", port);
+    #else
+    _snprintf_s(portStr, 6, _TRUNCATE, "%d", port);
+    #endif
+    int error = getaddrinfo(hostname, portStr, &hints, &server);
     if(error != 0 || !server) {
-        UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK, "DNS lookup of %s failed with error %s", hostname, gai_strerror(error));
+        UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                       "DNS lookup of %s failed with error %s",
+                       hostname, gai_strerror(error));
         return connection;
     }
 

+ 1 - 0
src/client/ua_client.c

@@ -472,6 +472,7 @@ static UA_StatusCode SessionHandshake(UA_Client *client) {
     UA_ByteString_copy(&client->channel->clientNonce, &request.clientNonce);
     request.requestedSessionTimeout = 1200000;
     request.maxResponseMessageSize = UA_INT32_MAX;
+    UA_String_copy(&client->endpointUrl, &request.endpointUrl);
 
     UA_CreateSessionResponse response;
     UA_CreateSessionResponse_init(&response);

+ 6 - 6
src/server/ua_server_worker.c

@@ -146,7 +146,7 @@ emptyDispatchQueue(UA_Server *server) {
 struct RepeatedJob {
     LIST_ENTRY(RepeatedJob) next;  /* Next element in the list */
     UA_DateTime nextTime;          /* The next time when the jobs are to be executed */
-    UA_UInt32 interval;            /* Interval in 100ns resolution */
+    UA_UInt64 interval;            /* Interval in 100ns resolution */
     UA_Guid id;                    /* Id of the repeated job */
     UA_Job job;                    /* The job description itself */
 };
@@ -174,17 +174,17 @@ addRepeatedJob(UA_Server *server, struct RepeatedJob * UA_RESTRICT rj)
 
 UA_StatusCode
 UA_Server_addRepeatedJob(UA_Server *server, UA_Job job,
-                         UA_UInt32 interval, UA_Guid *jobId) {
+                         UA_UInt32 intervalMs, UA_Guid *jobId) {
     /* the interval needs to be at least 5ms */
-    if(interval < 5)
+    if(intervalMs < 5)
         return UA_STATUSCODE_BADINTERNALERROR;
-    interval *= (UA_UInt32)UA_MSEC_TO_DATETIME; // from ms to 100ns resolution
+    UA_UInt64 interval = (UA_UInt64)intervalMs * (UA_UInt64)UA_MSEC_TO_DATETIME; // from ms to 100ns resolution
 
     /* Create and fill the repeated job structure */
     struct RepeatedJob *rj = UA_malloc(sizeof(struct RepeatedJob));
     if(!rj)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    rj->nextTime = UA_DateTime_nowMonotonic() + interval;
+    rj->nextTime = UA_DateTime_nowMonotonic() + (UA_Int64) interval;
     rj->interval = interval;
     rj->id = UA_Guid_random();
     rj->job = job;
@@ -235,7 +235,7 @@ processRepeatedJobs(UA_Server *server, UA_DateTime current) {
 #endif
 
         /* Set the time for the next execution */
-        rj->nextTime += rj->interval;
+        rj->nextTime += (UA_Int64)rj->interval;
         if(rj->nextTime < current)
             rj->nextTime = current;
 

+ 62 - 74
src/server/ua_services_attribute.c

@@ -6,24 +6,8 @@
 /******************/
 
 static size_t
-readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number) {
-    UA_UInt32 n = 0;
-    size_t progress = 0;
-    /* read numbers until the end or a non-number character appears */
-    while(progress < buflen) {
-        UA_Byte c = buf[progress];
-        if('0' > c || '9' < c)
-            break;
-        n = (n*10) + (UA_UInt32)(c-'0');
-        progress++;
-    }
-    *number = n;
-    return progress;
-}
-
-static size_t
-readDimension(UA_Byte *buf, size_t buflen, struct UA_NumericRangeDimension *dim) {
-    size_t progress = readNumber(buf, buflen, &dim->min);
+readDimension(UA_Byte *buf, size_t buflen, UA_NumericRangeDimension *dim) {
+    size_t progress = UA_readNumber(buf, buflen, &dim->min);
     if(progress == 0)
         return 0;
     if(buflen <= progress + 1 || buf[progress] != ':') {
@@ -32,12 +16,12 @@ readDimension(UA_Byte *buf, size_t buflen, struct UA_NumericRangeDimension *dim)
     }
 
     progress++;
-    size_t progress2 = readNumber(&buf[progress], buflen - progress, &dim->max);
+    size_t progress2 = UA_readNumber(&buf[progress], buflen - progress, &dim->max);
     if(progress2 == 0)
         return 0;
 
     /* invalid range */
-    if(dim->min >= dim->max)
+    if(dim->min > dim->max)
         return 0;
     
     return progress + progress2;
@@ -49,14 +33,14 @@ static
 UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range) {
     size_t idx = 0;
     size_t dimensionsMax = 0;
-    struct UA_NumericRangeDimension *dimensions = NULL;
+    UA_NumericRangeDimension *dimensions = NULL;
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     size_t offset = 0;
     while(true) {
         /* alloc dimensions */
         if(idx >= dimensionsMax) {
-            struct UA_NumericRangeDimension *newds;
-            size_t newdssize = sizeof(struct UA_NumericRangeDimension) * (dimensionsMax + 2);
+            UA_NumericRangeDimension *newds;
+            size_t newdssize = sizeof(UA_NumericRangeDimension) * (dimensionsMax + 2);
             newds = UA_realloc(dimensions, newdssize);
             if(!newds) {
                 retval = UA_STATUSCODE_BADOUTOFMEMORY;
@@ -582,22 +566,20 @@ UA_StatusCode UA_Server_editNode(UA_Server *server, UA_Session *session,
     }
 
 static UA_StatusCode
-Service_Write_single_ValueDataSource(UA_Server *server, UA_Session *session,
-                                     const UA_VariableNode *node, const UA_WriteValue *wvalue)
-{
+WriteToDataSource(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)
+    if(!node->value.dataSource.write)
         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 {
+
+    if(wvalue->indexRange.length > 0) {
+        /* write with an indexrange */
+        UA_StatusCode retval;
         UA_NumericRange range;
         retval = parse_numericrange(&wvalue->indexRange, &range);
         if(retval != UA_STATUSCODE_GOOD)
@@ -605,8 +587,12 @@ Service_Write_single_ValueDataSource(UA_Server *server, UA_Session *session,
         retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId,
                                               &wvalue->value.value, &range);
         UA_free(range.dimensions);
+        return retval;
     }
-    return retval;
+
+    /* write normal */
+    return node->value.dataSource.write(node->value.dataSource.handle,
+                                        node->nodeId, &wvalue->value.value, NULL);
 }
 
 enum type_equivalence {
@@ -626,7 +612,7 @@ static enum type_equivalence typeEquivalence(const UA_DataType *t) {
 }
 
 static UA_StatusCode
-CopyValueIntoNode(UA_VariableNode *node, const UA_WriteValue *wvalue) {
+CopyIntoValueAttribute(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);
@@ -644,7 +630,8 @@ CopyValueIntoNode(UA_VariableNode *node, const UA_WriteValue *wvalue) {
     }
 
     /* The nodeid on the wire may be != the nodeid in the node: opaque types,
-       enums and bytestrings. nodeV contains the correct type definition. */
+       enums and bytestrings. newV contains the correct type definition after
+       the following paragraph */
     UA_Variant *oldV = &node->value.variant.value;
     const UA_Variant *newV = &wvalue->value.value;
     UA_Variant cast_v;
@@ -660,41 +647,43 @@ CopyValueIntoNode(UA_VariableNode *node, const UA_WriteValue *wvalue) {
         } 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;
+            UA_ByteString *str = (UA_ByteString*)newV->data;
+            cast_v.type = &UA_TYPES[UA_TYPES_BYTE];
             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;
+            retval = UA_STATUSCODE_BADTYPEMISMATCH;
+            goto cleanup;
         }
     }
 
+    /* write the value */
     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);
+
+    /* post-write callback */
     if(node->value.variant.callback.onWrite)
-        node->value.variant.callback.onWrite(node->value.variant.callback.handle, node->nodeId,
-                                             &node->value.variant.value, rangeptr);
+        node->value.variant.callback.onWrite(node->value.variant.callback.handle,
+                                             node->nodeId, &node->value.variant.value,
+                                             rangeptr);
+ cleanup:
     if(rangeptr)
         UA_free(range.dimensions);
     return retval;
 }
 
+/* this function implements the main part of the write service */
 static UA_StatusCode
 CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
                       UA_Node *node, const UA_WriteValue *wvalue) {
     if(!wvalue->value.hasValue)
         return UA_STATUSCODE_BADTYPEMISMATCH;
 
-    void *value = wvalue->value.value.data;
-    void *target = NULL;
-    const UA_DataType *attr_type = NULL;
-
+    const void *value = wvalue->value.value.data;
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
     switch(wvalue->attributeId) {
     case UA_ATTRIBUTEID_NODEID:
@@ -704,101 +693,100 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
         break;
     case UA_ATTRIBUTEID_BROWSENAME:
         CHECK_DATATYPE(QUALIFIEDNAME);
-        target = &node->browseName;
-        attr_type = &UA_TYPES[UA_TYPES_QUALIFIEDNAME];
+        UA_QualifiedName_deleteMembers(&node->browseName);
+        UA_QualifiedName_copy(value, &node->browseName);
         break;
     case UA_ATTRIBUTEID_DISPLAYNAME:
         CHECK_DATATYPE(LOCALIZEDTEXT);
-        target = &node->displayName;
-        attr_type = &UA_TYPES[UA_TYPES_LOCALIZEDTEXT];
+        UA_LocalizedText_deleteMembers(&node->displayName);
+        UA_LocalizedText_copy(value, &node->displayName);
         break;
     case UA_ATTRIBUTEID_DESCRIPTION:
         CHECK_DATATYPE(LOCALIZEDTEXT);
-        target = &node->description;
-        attr_type = &UA_TYPES[UA_TYPES_LOCALIZEDTEXT];
+        UA_LocalizedText_deleteMembers(&node->description);
+        UA_LocalizedText_copy(value, &node->description);
         break;
     case UA_ATTRIBUTEID_WRITEMASK:
         CHECK_DATATYPE(UINT32);
-        node->writeMask = *(UA_UInt32*)value;
+        node->writeMask = *(const UA_UInt32*)value;
         break;
     case UA_ATTRIBUTEID_USERWRITEMASK:
         CHECK_DATATYPE(UINT32);
-        node->userWriteMask = *(UA_UInt32*)value;
+        node->userWriteMask = *(const UA_UInt32*)value;
         break;
     case UA_ATTRIBUTEID_ISABSTRACT:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_OBJECTTYPE | UA_NODECLASS_REFERENCETYPE |
                               UA_NODECLASS_VARIABLETYPE | UA_NODECLASS_DATATYPE);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_ObjectTypeNode*)node)->isAbstract = *(UA_Boolean*)value;
+        ((UA_ObjectTypeNode*)node)->isAbstract = *(const UA_Boolean*)value;
         break;
     case UA_ATTRIBUTEID_SYMMETRIC:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_ReferenceTypeNode*)node)->symmetric = *(UA_Boolean*)value;
+        ((UA_ReferenceTypeNode*)node)->symmetric = *(const UA_Boolean*)value;
         break;
     case UA_ATTRIBUTEID_INVERSENAME:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_REFERENCETYPE);
         CHECK_DATATYPE(LOCALIZEDTEXT);
-        target = &((UA_ReferenceTypeNode*)node)->inverseName;
-        attr_type = &UA_TYPES[UA_TYPES_LOCALIZEDTEXT];
+        UA_LocalizedText_deleteMembers(&((UA_ReferenceTypeNode*)node)->inverseName);
+        UA_LocalizedText_copy(value, &((UA_ReferenceTypeNode*)node)->inverseName);
         break;
     case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_ViewNode*)node)->containsNoLoops = *(UA_Boolean*)value;
+        ((UA_ViewNode*)node)->containsNoLoops = *(const UA_Boolean*)value;
         break;
     case UA_ATTRIBUTEID_EVENTNOTIFIER:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VIEW | UA_NODECLASS_OBJECT);
         CHECK_DATATYPE(BYTE);
-        ((UA_ViewNode*)node)->eventNotifier = *(UA_Byte*)value;
+        ((UA_ViewNode*)node)->eventNotifier = *(const UA_Byte*)value;
         break;
     case UA_ATTRIBUTEID_VALUE:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
         if(((const UA_VariableNode*)node)->valueSource == UA_VALUESOURCE_VARIANT)
-            retval = CopyValueIntoNode((UA_VariableNode*)node, wvalue);
+            retval = CopyIntoValueAttribute((UA_VariableNode*)node, wvalue);
         else
-            retval = Service_Write_single_ValueDataSource(server, session,
-                                                          (const UA_VariableNode*)node,
-                                                          wvalue);
+            /* TODO: Don't make a copy of the node in the multithreaded case */
+            retval = WriteToDataSource(server, session,
+                                       (const UA_VariableNode*)node, wvalue);
         break;
     case UA_ATTRIBUTEID_ACCESSLEVEL:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
         CHECK_DATATYPE(BYTE);
-        ((UA_VariableNode*)node)->accessLevel = *(UA_Byte*)value;
+        ((UA_VariableNode*)node)->accessLevel = *(const UA_Byte*)value;
         break;
     case UA_ATTRIBUTEID_USERACCESSLEVEL:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
         CHECK_DATATYPE(BYTE);
-        ((UA_VariableNode*)node)->userAccessLevel = *(UA_Byte*)value;
+        ((UA_VariableNode*)node)->userAccessLevel = *(const UA_Byte*)value;
         break;
     case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
         CHECK_DATATYPE(DOUBLE);
-        ((UA_VariableNode*)node)->minimumSamplingInterval = *(UA_Double*)value;
+        ((UA_VariableNode*)node)->minimumSamplingInterval = *(const UA_Double*)value;
         break;
     case UA_ATTRIBUTEID_HISTORIZING:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_VariableNode*)node)->historizing = *(UA_Boolean*)value;
+        ((UA_VariableNode*)node)->historizing = *(const UA_Boolean*)value;
         break;
     case UA_ATTRIBUTEID_EXECUTABLE:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_MethodNode*)node)->executable = *(UA_Boolean*)value;
+        ((UA_MethodNode*)node)->executable = *(const UA_Boolean*)value;
         break;
     case UA_ATTRIBUTEID_USEREXECUTABLE:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
         CHECK_DATATYPE(BOOLEAN);
-        ((UA_MethodNode*)node)->userExecutable = *(UA_Boolean*)value;
+        ((UA_MethodNode*)node)->userExecutable = *(const UA_Boolean*)value;
         break;
     default:
         retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
         break;
     }
-    if(attr_type) {
-        UA_deleteMembers(target, attr_type);
-        retval = UA_copy(value, target, attr_type);
-    }
+    if(retval != UA_STATUSCODE_GOOD)
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "WriteRequest returned status code 0x%08x", retval);
     return retval;
 }
 

+ 2 - 1
src/server/ua_services_call.c

@@ -92,7 +92,8 @@ satisfySignature(UA_Server *server, const UA_Variant *var, const UA_Argument *ar
         if(arg->arrayDimensionsSize != varDimsSize)
             return UA_STATUSCODE_BADINVALIDARGUMENT;
         for(size_t i = 0; i < varDimsSize; i++) {
-            if((UA_Int32)arg->arrayDimensions[i] != varDims[i])
+            // A value of 0 for an individual dimension indicates that the dimension has a variable	length.
+            if((UA_Int32)arg->arrayDimensions[i]!=0 && (UA_Int32)arg->arrayDimensions[i] != varDims[i])
                 return UA_STATUSCODE_BADINVALIDARGUMENT;
         }
     }

+ 135 - 0
src/ua_connection.c

@@ -161,3 +161,138 @@ void UA_Connection_attachSecureChannel(UA_Connection *connection, UA_SecureChann
 #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6)
 #pragma GCC diagnostic pop
 #endif
+
+UA_StatusCode
+UA_EndpointUrl_split_ptr(const char *endpointUrl, char *hostname,
+                         const char ** port, const char **path) {
+    if (!endpointUrl || !hostname)
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+
+    size_t urlLength = strlen(endpointUrl);
+    if(urlLength < 10 || urlLength >= 256)
+        return UA_STATUSCODE_BADOUTOFRANGE;
+
+    if(strncmp(endpointUrl, "opc.tcp://", 10) != 0)
+        return UA_STATUSCODE_BADATTRIBUTEIDINVALID;
+
+    if (urlLength == 10) {
+        hostname[0] = '\0';
+        port = NULL;
+        *path = NULL;
+    }
+
+    /* where does the port begin? */
+    size_t portpos = 10;
+    // opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path
+    // if ip6, then end not found, otherwise we are fine
+    UA_Boolean ip6_end_found = endpointUrl[portpos] != '[';
+    for(; portpos < urlLength; portpos++) {
+        if (!ip6_end_found) {
+            if (endpointUrl[portpos] == ']')
+                ip6_end_found = UA_TRUE;
+            continue;
+        }
+
+        if(endpointUrl[portpos] == ':' || endpointUrl[portpos] == '/')
+            break;
+    }
+
+    memcpy(hostname, &endpointUrl[10], portpos - 10);
+    hostname[portpos-10] = 0;
+
+    if(port) {
+        if (portpos < urlLength - 1) {
+            if (endpointUrl[portpos] == '/')
+                *port = NULL;
+            else
+                *port = &endpointUrl[portpos + 1];
+        } else {
+            *port = NULL;
+        }
+    }
+
+    if(path) {
+        size_t pathpos = portpos < urlLength ? portpos : 10;
+        for(; pathpos < urlLength; pathpos++) {
+            if(endpointUrl[pathpos] == '/')
+                break;
+        }
+        if (pathpos < urlLength-1)
+            *path = &endpointUrl[pathpos+1]; // do not include slash in path
+        else
+            *path = NULL;
+    }
+
+    return UA_STATUSCODE_GOOD;
+}
+
+
+UA_StatusCode
+UA_EndpointUrl_split(const char *endpointUrl, char *hostname,
+                     UA_UInt16 * port, const char ** path) {
+    const char* portTmp = NULL;
+    const char* pathTmp = NULL;
+    UA_StatusCode retval = UA_EndpointUrl_split_ptr(endpointUrl, hostname, &portTmp, &pathTmp);
+    if(retval != UA_STATUSCODE_GOOD) {
+        if(hostname)
+            hostname[0] = '\0';
+        return retval;
+    }
+    if(!port && !path)
+        return UA_STATUSCODE_GOOD;
+
+    char portStr[6];
+    portStr[0] = '\0';
+    if(portTmp) {
+        size_t portLen;
+        if (pathTmp)
+            portLen = (size_t)(pathTmp-portTmp-1);
+        else
+            portLen = strlen(portTmp);
+
+        if (portLen > 5) // max is 65535
+            return UA_STATUSCODE_BADOUTOFRANGE;
+
+        memcpy(portStr, portTmp, portLen);
+        portStr[portLen]='\0';
+
+        if(port) {
+            for (size_t i=0; i<6; i++) {
+                if (portStr[i] == 0)
+                    break;
+                if (portStr[i] < '0' || portStr[i] > '9')
+                    return UA_STATUSCODE_BADOUTOFRANGE;
+            }
+
+            UA_UInt32 p;
+            UA_readNumber((UA_Byte*)portStr, 6, &p);
+            if (p>65535)
+                return UA_STATUSCODE_BADOUTOFRANGE;
+            *port = (UA_UInt16)p;
+        }
+    } else {
+        if (port)
+            *port = 0;
+    }
+    if (path)
+        *path = pathTmp;
+    return UA_STATUSCODE_GOOD;
+}
+
+
+size_t UA_readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number) {
+    if (!buf)
+        return 0;
+    UA_UInt32 n = 0;
+    size_t progress = 0;
+    /* read numbers until the end or a non-number character appears */
+    while(progress < buflen) {
+        UA_Byte c = buf[progress];
+        if('0' > c || '9' < c)
+            break;
+        n = (n*10) + (UA_UInt32)(c-'0');
+        progress++;
+    }
+    *number = n;
+    return progress;
+}

+ 14 - 0
src/ua_connection_internal.h

@@ -28,4 +28,18 @@ UA_Connection_completeMessages(UA_Connection *connection, UA_ByteString * UA_RES
 void UA_EXPORT UA_Connection_detachSecureChannel(UA_Connection *connection);
 void UA_EXPORT UA_Connection_attachSecureChannel(UA_Connection *connection, UA_SecureChannel *channel);
 
+/**
+ * EndpointURL helper
+ */
+
+/**
+ * Split the given endpoint url into hostname and port. Some of the chunks are returned as pointer.
+ * @param endpointUrl The endpoint URL to split up
+ * @param hostname the target array for hostname. Has to be at least 256 size.
+ * @param port if url contains port, it will point to the beginning of port. NULL otherwise. It may also include the path part, thus stop at position of path pointer, if it is not NULL.
+ * @param path points to the first occurance of '/' after the port or NULL if no path in url
+ * @return UA_STATUSCODE_BADOUTOFRANGE if url too long, UA_STATUSCODE_BADATTRIBUTEIDINVALID if url not starting with 'opc.tcp://', UA_STATUSCODE_GOOD on success
+ */
+UA_StatusCode UA_EXPORT UA_EndpointUrl_split_ptr(const char *endpointUrl, char *hostname, const char ** port, const char ** path);
+
 #endif /* UA_CONNECTION_INTERNAL_H_ */

+ 189 - 106
src/ua_types.c

@@ -351,17 +351,62 @@ Variant_copy(UA_Variant const *src, UA_Variant *dst, const UA_DataType *_) {
     return UA_STATUSCODE_GOOD;
 }
 
-/**
- * Test if a range is compatible with a variant. If yes, the following values are set:
+void
+UA_Variant_setScalar(UA_Variant *v, void * UA_RESTRICT p,
+                     const UA_DataType *type) {
+    UA_Variant_init(v);
+    v->type = type;
+    v->arrayLength = 0;
+    v->data = p;
+}
+
+UA_StatusCode
+UA_Variant_setScalarCopy(UA_Variant *v, const void *p,
+                         const UA_DataType *type) {
+    void *new = UA_malloc(type->memSize);
+    if(!new)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    UA_StatusCode retval = UA_copy(p, new, type);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_free(new);
+        //cppcheck-suppress memleak
+        return retval;
+    }
+    UA_Variant_setScalar(v, new, type);
+    //cppcheck-suppress memleak
+    return UA_STATUSCODE_GOOD;
+}
+
+void UA_Variant_setArray(UA_Variant *v, void * UA_RESTRICT array,
+                         size_t arraySize, const UA_DataType *type) {
+    UA_Variant_init(v);
+    v->data = array;
+    v->arrayLength = arraySize;
+    v->type = type;
+}
+
+UA_StatusCode
+UA_Variant_setArrayCopy(UA_Variant *v, const void *array,
+                        size_t arraySize, const UA_DataType *type) {
+    UA_Variant_init(v);
+    UA_StatusCode retval = UA_Array_copy(array, arraySize, &v->data, type);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+    v->arrayLength = arraySize;
+    v->type = type;
+    return UA_STATUSCODE_GOOD;
+}
+
+/* Range-wise access to Variants */
+
+/* Test if a range is compatible with a variant. If yes, the following values are set:
  * - total: how many elements are in the range
  * - block: how big is each contiguous block of elements in the variant that maps into the range
  * - stride: how many elements are between the blocks (beginning to beginning)
- * - first: where does the first block begin
- */
+ * - first: where does the first block begin */
 static UA_StatusCode
-processRangeDefinition(const UA_Variant *v, const UA_NumericRange range,
-                       size_t *total, size_t *block, size_t *stride,
-                       size_t *first) {
+computeStrides(const UA_Variant *v, const UA_NumericRange range,
+               size_t *total, size_t *block, size_t *stride, size_t *first) {
     /* Test the integrity of the source variant dimensions */
     size_t dims_count = 1;
     UA_UInt32 elements = 1;
@@ -418,14 +463,76 @@ processRangeDefinition(const UA_Variant *v, const UA_NumericRange range,
     return UA_STATUSCODE_GOOD;
 }
 
+/* Is the type string-like? */
+static UA_Boolean
+isStringLike(const UA_DataType *type) {
+    if(type->membersSize == 1 && type->members[0].isArray &&
+       type->members[0].namespaceZero &&
+       type->members[0].memberTypeIndex == UA_TYPES_BYTE)
+        return true;
+    return false;
+}
+
+static UA_StatusCode
+copySubString(const UA_String *src, UA_String *dst,
+              const UA_NumericRangeDimension *dim) {
+    if(dim->min > dim->max)
+        return UA_STATUSCODE_BADINDEXRANGEINVALID;
+    if(dim->max >= src->length)
+        return UA_STATUSCODE_BADINDEXRANGENODATA;
+
+    size_t length = dim->max - dim->min + 1;
+    UA_StatusCode retval = UA_ByteString_allocBuffer(dst, length);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    memcpy(dst->data, &src->data[dim->min], length);
+    return UA_STATUSCODE_GOOD;
+}
+
 UA_StatusCode
-UA_Variant_copyRange(const UA_Variant *src, UA_Variant *dst,
+UA_Variant_copyRange(const UA_Variant *orig_src, UA_Variant *dst,
                      const UA_NumericRange range) {
+    UA_Boolean isScalar = UA_Variant_isScalar(orig_src);
+    UA_Boolean stringLike = isStringLike(orig_src->type);
+    const UA_Variant *src = orig_src;
+    UA_Variant arraySrc;
+
+    /* Extract the range for copying at this level. The remaining range is dealt
+     * with in the "scalar" type that may define an array by itself (string,
+     * variant, ...). */
+    UA_NumericRange thisrange, nextrange;
+    UA_NumericRangeDimension scalarThisDimension = (UA_NumericRangeDimension){
+        .min = 0, .max = 0}; /* a single entry */
+    if(isScalar) {
+        /* Replace scalar src with array of length 1 */
+        arraySrc = *src;
+        arraySrc.arrayLength = 1;
+        src = &arraySrc;
+        /* Deal with all range dimensions within the scalar */
+        thisrange.dimensions = &scalarThisDimension;
+        thisrange.dimensionsSize = 1;
+        nextrange = range;
+    } else {
+        /* Deal with as many range dimensions as possible right now */
+        size_t dims = src->arrayDimensionsSize;
+        if(dims == 0)
+            dims = 1;
+        if(dims > range.dimensionsSize)
+            return UA_STATUSCODE_BADINDEXRANGEINVALID;
+       thisrange = range;
+       thisrange.dimensionsSize = dims;
+       nextrange.dimensions = &range.dimensions[dims];
+       nextrange.dimensionsSize = range.dimensionsSize - dims;
+    }
+        
+    /* Compute the strides */
     size_t count, block, stride, first;
-    UA_StatusCode retval = processRangeDefinition(src, range, &count, &block,
-                                                  &stride, &first);
+    UA_StatusCode retval = computeStrides(src, thisrange, &count, &block, &stride, &first);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
+
+    /* Allocate the array */
     UA_Variant_init(dst);
     size_t elem_size = src->type->memSize;
     dst->data = UA_malloc(elem_size * count);
@@ -436,94 +543,98 @@ UA_Variant_copyRange(const UA_Variant *src, UA_Variant *dst,
     size_t block_count = count / block;
     uintptr_t nextdst = (uintptr_t)dst->data;
     uintptr_t nextsrc = (uintptr_t)src->data + (elem_size * first);
-    if(src->type->fixedSize) {
-        for(size_t i = 0; i < block_count; i++) {
-            memcpy((void*)nextdst, (void*)nextsrc, elem_size * block);
-            nextdst += block * elem_size;
-            nextsrc += stride * elem_size;
+    if(nextrange.dimensionsSize == 0) {
+        /* no nextrange */
+        if(src->type->fixedSize) {
+            for(size_t i = 0; i < block_count; i++) {
+                memcpy((void*)nextdst, (void*)nextsrc, elem_size * block);
+                nextdst += block * elem_size;
+                nextsrc += stride * elem_size;
+            }
+        } else {
+            for(size_t i = 0; i < block_count; i++) {
+                for(size_t j = 0; j < block && retval == UA_STATUSCODE_GOOD; j++) {
+                    retval = UA_copy((const void*)nextsrc, (void*)nextdst, src->type);
+                    nextdst += elem_size;
+                    nextsrc += elem_size;
+                }
+                nextsrc += (stride - block) * elem_size;
+            }
         }
     } else {
+        /* nextrange can only be used for variants and stringlike with remaining
+         * range of dimension 1 */
+        if(src->type != &UA_TYPES[UA_TYPES_VARIANT]) {
+            if(!stringLike)
+                retval = UA_STATUSCODE_BADINDEXRANGENODATA;
+            if(nextrange.dimensionsSize != 1)
+                retval = UA_STATUSCODE_BADINDEXRANGENODATA;
+        }
+
+        /* copy the content */
         for(size_t i = 0; i < block_count; i++) {
             for(size_t j = 0; j < block && retval == UA_STATUSCODE_GOOD; j++) {
-                retval = UA_copy((const void*)nextsrc, (void*)nextdst, src->type);
+                if(stringLike)
+                    retval = copySubString((const UA_String*)nextsrc,
+                                           (UA_String*)nextdst, nextrange.dimensions);
+                else
+                    retval = UA_Variant_copyRange((const UA_Variant*)nextsrc,
+                                                  (UA_Variant*)nextdst, nextrange);
                 nextdst += elem_size;
                 nextsrc += elem_size;
             }
             nextsrc += (stride - block) * elem_size;
         }
-        if(retval != UA_STATUSCODE_GOOD) {
-            size_t copied = ((nextdst - elem_size) - (uintptr_t)dst->data) / elem_size;
-            UA_Array_delete(dst->data, copied, src->type);
-            dst->data = NULL;
-            return retval;
-        }
     }
-    dst->arrayLength = count;
+
+    /* Clean up if copying failed */
+    if(retval != UA_STATUSCODE_GOOD) {
+        size_t copied = ((nextdst - elem_size) - (uintptr_t)dst->data) / elem_size;
+        UA_Array_delete(dst->data, copied, src->type);
+        dst->data = NULL;
+        return retval;
+    }
+
+    /* Done if scalar */
     dst->type = src->type;
+    if(isScalar)
+        return retval;
 
-    /* Copy the range dimensions */
+    /* Finish the array */
+    dst->arrayLength = count;
     if(src->arrayDimensionsSize > 0) {
-        dst->arrayDimensions = UA_Array_new(src->arrayDimensionsSize,
-                                            &UA_TYPES[UA_TYPES_UINT32]);
+        dst->arrayDimensions = UA_Array_new(thisrange.dimensionsSize, &UA_TYPES[UA_TYPES_UINT32]);
         if(!dst->arrayDimensions) {
             Variant_deletemembers(dst, NULL);
-            memset(dst, 0, sizeof(UA_Variant)); /* init */
             return UA_STATUSCODE_BADOUTOFMEMORY;
         }
-        dst->arrayDimensionsSize = src->arrayDimensionsSize;
-        for(size_t k = 0; k < src->arrayDimensionsSize; k++)
+        dst->arrayDimensionsSize = thisrange.dimensionsSize;
+        for(size_t k = 0; k < thisrange.dimensionsSize; k++)
             dst->arrayDimensions[k] =
-                (UA_Int32)(range.dimensions[k].max - range.dimensions[k].min + 1);
+                (UA_Int32)(thisrange.dimensions[k].max - thisrange.dimensions[k].min + 1);
     }
     return UA_STATUSCODE_GOOD;
 }
 
-UA_StatusCode
-UA_Variant_setRange(UA_Variant *v, void * UA_RESTRICT array, size_t arraySize,
-                    const UA_NumericRange range) {
-    size_t count, block, stride, first;
-    UA_StatusCode retval = processRangeDefinition(v, range, &count, &block,
-                                                  &stride, &first);
-    if(retval != UA_STATUSCODE_GOOD)
-        return retval;
-    if(count != arraySize)
-        return UA_STATUSCODE_BADINDEXRANGEINVALID;
-
-    size_t block_count = count / block;
-    size_t elem_size = v->type->memSize;
-    uintptr_t nextdst = (uintptr_t)v->data + (first * elem_size);
-    uintptr_t nextsrc = (uintptr_t)array;
-    for(size_t i = 0; i < block_count; i++) {
-        if(!v->type->fixedSize) {
-            for(size_t j = 0; j < block; j++) {
-                UA_deleteMembers_noInit((void*)nextdst, v->type);
-                nextdst += elem_size;
-            }
-            nextdst -= block * elem_size;
-        }
-        memcpy((void*)nextdst, (void*)nextsrc, elem_size * block);
-        nextsrc += block * elem_size;
-        nextdst += stride * elem_size;
-    }
-    return UA_STATUSCODE_GOOD;
-}
-
-UA_StatusCode
-UA_Variant_setRangeCopy(UA_Variant *v, const void *array, size_t arraySize,
-                        const UA_NumericRange range) {
+/* TODO: Allow ranges to reach inside a scalars that are array-like, e.g.
+   variant and strings. This is already possible for reading... */
+static UA_StatusCode
+Variant_setRange(UA_Variant *v, void *array, size_t arraySize,
+                 const UA_NumericRange range, UA_Boolean copy) {
+    /* Compute the strides */
     size_t count, block, stride, first;
-    UA_StatusCode retval = processRangeDefinition(v, range, &count, &block,
-                                                  &stride, &first);
+    UA_StatusCode retval = computeStrides(v, range, &count, &block, &stride, &first);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
     if(count != arraySize)
         return UA_STATUSCODE_BADINDEXRANGEINVALID;
 
+    /* Transfer the content */
     size_t block_count = count / block;
     size_t elem_size = v->type->memSize;
     uintptr_t nextdst = (uintptr_t)v->data + (first * elem_size);
     uintptr_t nextsrc = (uintptr_t)array;
-    if(v->type->fixedSize) {
+    if(v->type->fixedSize || !copy) {
         for(size_t i = 0; i < block_count; i++) {
             memcpy((void*)nextdst, (void*)nextsrc, elem_size * block);
             nextsrc += block * elem_size;
@@ -540,53 +651,25 @@ UA_Variant_setRangeCopy(UA_Variant *v, const void *array, size_t arraySize,
             nextdst += (stride - block) * elem_size;
         }
     }
-    return retval;
-}
 
-void
-UA_Variant_setScalar(UA_Variant *v, void * UA_RESTRICT p,
-                     const UA_DataType *type) {
-    UA_Variant_init(v);
-    v->type = type;
-    v->arrayLength = 0;
-    v->data = p;
-}
+    /* If pointers were transferred, initialize original array to prevent
+     * reuse */
+    if(!copy && !v->type->fixedSize)
+        memset(array, 0, sizeof(elem_size)*arraySize);
 
-UA_StatusCode
-UA_Variant_setScalarCopy(UA_Variant *v, const void *p,
-                         const UA_DataType *type) {
-    void *new = UA_malloc(type->memSize);
-    if(!new)
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-    UA_StatusCode retval = UA_copy(p, new, type);
-    if(retval != UA_STATUSCODE_GOOD) {
-        UA_free(new);
-        //cppcheck-suppress memleak
-        return retval;
-    }
-    UA_Variant_setScalar(v, new, type);
-    //cppcheck-suppress memleak
-    return UA_STATUSCODE_GOOD;
+    return retval;
 }
 
-void UA_Variant_setArray(UA_Variant *v, void * UA_RESTRICT array,
-                         size_t arraySize, const UA_DataType *type) {
-    UA_Variant_init(v);
-    v->data = array;
-    v->arrayLength = arraySize;
-    v->type = type;
+UA_StatusCode
+UA_Variant_setRange(UA_Variant *v, void * UA_RESTRICT array, size_t arraySize,
+                    const UA_NumericRange range) {
+    return Variant_setRange(v, array, arraySize, range, false);
 }
 
 UA_StatusCode
-UA_Variant_setArrayCopy(UA_Variant *v, const void *array,
-                        size_t arraySize, const UA_DataType *type) {
-    UA_Variant_init(v);
-    UA_StatusCode retval = UA_Array_copy(array, arraySize, &v->data, type);
-    if(retval != UA_STATUSCODE_GOOD)
-        return retval;
-    v->arrayLength = arraySize;
-    v->type = type;
-    return UA_STATUSCODE_GOOD;
+UA_Variant_setRangeCopy(UA_Variant *v, const void *array, size_t arraySize,
+                        const UA_NumericRange range) {
+    return Variant_setRange(v, (void*)(uintptr_t)array, arraySize, range, true);
 }
 
 /* LocalizedText */

+ 14 - 6
tests/CMakeLists.txt

@@ -42,13 +42,17 @@ endmacro()
 # the unit test are built directly on the open62541 object files. so they can
 # access symbols that are hidden/not exported to the shared library
 
-add_executable(check_builtin check_builtin.c $<TARGET_OBJECTS:open62541-object>)
-target_link_libraries(check_builtin ${LIBS})
-add_test_valgrind(builtin ${CMAKE_CURRENT_BINARY_DIR}/check_builtin)
+add_executable(check_types_builtin check_types_builtin.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_types_builtin ${LIBS})
+add_test(types_builtin ${CMAKE_CURRENT_BINARY_DIR}/check_types_builtin)
 
-add_executable(check_memory check_memory.c $<TARGET_OBJECTS:open62541-object>)
-target_link_libraries(check_memory ${LIBS})
-add_test_valgrind(memory ${CMAKE_CURRENT_BINARY_DIR}/check_memory)
+add_executable(check_types_memory check_types_memory.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_types_memory ${LIBS})
+add_test(types_memory ${CMAKE_CURRENT_BINARY_DIR}/check_types_memory)
+
+add_executable(check_types_range check_types_range.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_types_range ${LIBS})
+add_test(types_range ${CMAKE_CURRENT_BINARY_DIR}/check_types_range)
 
 add_executable(check_chunking check_chunking.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_chunking ${LIBS})
@@ -146,3 +150,7 @@ add_test_valgrind(check_client ${CMAKE_CURRENT_BINARY_DIR}/check_client)
 add_executable(check_client_subscriptions check_client_subscriptions.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_client_subscriptions ${LIBS})
 add_test_valgrind(check_client_subscriptions ${CMAKE_CURRENT_BINARY_DIR}/check_client_subscriptions)
+
+add_executable(check_utils check_utils.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_utils ${LIBS})
+add_test(check_utils ${CMAKE_CURRENT_BINARY_DIR}/check_utils)

+ 3 - 3
tests/check_server_jobs.c

@@ -20,7 +20,7 @@ static void teardown(void) {
 UA_Boolean *executed;
 
 static void
-dummyJob(UA_Server *server, void *data) {
+dummyJob(UA_Server *serverPtr, void *data) {
     *executed = true;
 }
 
@@ -46,8 +46,8 @@ END_TEST
 UA_Guid *jobId;
 
 static void
-removeItselfJob(UA_Server *server, void *data) {
-    UA_Server_removeRepeatedJob(server, *jobId);
+removeItselfJob(UA_Server *serverPtr, void *data) {
+    UA_Server_removeRepeatedJob(serverPtr, *jobId);
 }
 
 START_TEST(Server_repeatedJobRemoveItself) {

+ 2 - 24
tests/check_services_attributes.c

@@ -16,9 +16,6 @@
 #include <urcu.h>
 #endif
 
-/* copied definition */
-UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range);
-
 static UA_StatusCode
 readCPUTemperature_broken(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
                           const UA_NumericRange *range, UA_DataValue *dataValue) {
@@ -1023,21 +1020,6 @@ START_TEST(WriteSingleDataSourceAttributeValue) {
     UA_Server_delete(server);
 } END_TEST
 
-START_TEST(numericRange) {
-    UA_NumericRange range;
-    const UA_String str = (UA_String){9, (UA_Byte*)"1:2,0:3,5"};
-    UA_StatusCode retval = parse_numericrange(&str, &range);
-    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    ck_assert_int_eq(range.dimensionsSize,3);
-    ck_assert_int_eq(range.dimensions[0].min,1);
-    ck_assert_int_eq(range.dimensions[0].max,2);
-    ck_assert_int_eq(range.dimensions[1].min,0);
-    ck_assert_int_eq(range.dimensions[1].max,3);
-    ck_assert_int_eq(range.dimensions[2].min,5);
-    ck_assert_int_eq(range.dimensions[2].max,5);
-    UA_free(range.dimensions);
-} END_TEST
-
 static Suite * testSuite_services_attributes(void) {
     Suite *s = suite_create("services_attributes_read");
 
@@ -1065,8 +1047,8 @@ static Suite * testSuite_services_attributes(void) {
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeHistorizingWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeExecutableWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleAttributeUserExecutableWithoutTimestamp);
-        tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeDataTypeWithoutTimestampFromBrokenSource);
-        tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeValueWithoutTimestamp);
+    tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeDataTypeWithoutTimestampFromBrokenSource);
+    tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeValueWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeDataTypeWithoutTimestamp);
     tcase_add_test(tc_readSingleAttributes, ReadSingleDataSourceAttributeArrayDimensionsWithoutTimestamp);
 
@@ -1099,10 +1081,6 @@ static Suite * testSuite_services_attributes(void) {
 
     suite_add_tcase(s, tc_writeSingleAttributes);
 
-    TCase *tc_parseNumericRange = tcase_create("parseNumericRange");
-    tcase_add_test(tc_parseNumericRange, numericRange);
-    suite_add_tcase(s, tc_parseNumericRange);
-
     return s;
 }
 

tests/check_builtin.c → tests/check_types_builtin.c


tests/check_memory.c → tests/check_types_memory.c


+ 103 - 0
tests/check_types_range.c

@@ -0,0 +1,103 @@
+#include "ua_types.h"
+#include "ua_types_generated_handling.h"
+#include "ua_util.h"
+#include "check.h"
+
+/* copied definition */
+UA_StatusCode parse_numericrange(const UA_String *str, UA_NumericRange *range);
+
+START_TEST(parseRange) {
+    UA_NumericRange range;
+    UA_String str = UA_STRING("1:2,0:3,5");
+    UA_StatusCode retval = parse_numericrange(&str, &range);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(range.dimensionsSize,3);
+    ck_assert_int_eq(range.dimensions[0].min,1);
+    ck_assert_int_eq(range.dimensions[0].max,2);
+    ck_assert_int_eq(range.dimensions[1].min,0);
+    ck_assert_int_eq(range.dimensions[1].max,3);
+    ck_assert_int_eq(range.dimensions[2].min,5);
+    ck_assert_int_eq(range.dimensions[2].max,5);
+    UA_free(range.dimensions);
+} END_TEST
+
+START_TEST(parseRangeMinEqualMax) {
+    UA_NumericRange range;
+    UA_String str = UA_STRING("1:2,1:1");
+    UA_StatusCode retval = parse_numericrange(&str, &range);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(range.dimensionsSize,2);
+    ck_assert_int_eq(range.dimensions[0].min,1);
+    ck_assert_int_eq(range.dimensions[0].max,2);
+    ck_assert_int_eq(range.dimensions[1].min,1);
+    ck_assert_int_eq(range.dimensions[1].max,1);
+    UA_free(range.dimensions);
+} END_TEST
+
+START_TEST(copySimpleArrayRange) {
+    UA_Variant v, v2;
+    UA_Variant_init(&v);
+    UA_Variant_init(&v2);
+    UA_UInt32 arr[5] = {1,2,3,4,5};
+    UA_Variant_setArray(&v, arr, 5, &UA_TYPES[UA_TYPES_UINT32]);
+
+    UA_NumericRange r;
+    UA_String sr = UA_STRING("1:3");
+    UA_StatusCode retval = parse_numericrange(&sr, &r);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Variant_copyRange(&v, &v2, r);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(3, v2.arrayLength);
+    ck_assert_int_eq(2, *(UA_UInt32*)v2.data);
+
+    UA_Variant_deleteMembers(&v2);
+    UA_free(r.dimensions);
+}
+END_TEST
+
+START_TEST(copyIntoStringArrayRange) {
+    UA_Variant v, v2;
+    UA_Variant_init(&v);
+    UA_Variant_init(&v2);
+    UA_String arr[2];
+    arr[0] = UA_STRING("abcd");
+    arr[1] = UA_STRING("wxyz");
+    UA_Variant_setArray(&v, arr, 5, &UA_TYPES[UA_TYPES_STRING]);
+
+    UA_NumericRange r;
+    UA_String sr = UA_STRING("0:1,1:2");
+    UA_StatusCode retval = parse_numericrange(&sr, &r);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+
+    retval = UA_Variant_copyRange(&v, &v2, r);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(2, v2.arrayLength);
+
+    UA_String s1 = UA_STRING("bc");
+    UA_String s2 = UA_STRING("xy");
+    UA_String *arr2 = v2.data;
+    ck_assert(UA_String_equal(&arr2[0], &s1));
+    ck_assert(UA_String_equal(&arr2[1], &s2));
+
+    UA_Variant_deleteMembers(&v2);
+    UA_free(r.dimensions);
+}
+END_TEST
+
+int main(void) {
+    Suite *s  = suite_create("Test Variant Range Access");
+    TCase *tc = tcase_create("test cases");
+    tcase_add_test(tc, parseRange);
+    tcase_add_test(tc, parseRangeMinEqualMax);
+    tcase_add_test(tc, copySimpleArrayRange);
+    tcase_add_test(tc, copyIntoStringArrayRange);
+    suite_add_tcase(s, tc);
+
+    SRunner *sr = srunner_create(s);
+    srunner_run_all (sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 134 - 0
tests/check_utils.c

@@ -0,0 +1,134 @@
+#include <stdlib.h>
+
+#include "ua_types.h"
+#include "ua_client.h"
+#include "check.h"
+
+START_TEST(EndpointUrl_split) {
+    // check for null
+    ck_assert_uint_eq(UA_EndpointUrl_split(NULL, NULL, NULL, NULL), UA_STATUSCODE_BADINVALIDARGUMENT);
+
+    char hostname[256];
+    UA_UInt16 port;
+    const char* path;
+
+    // check for max url length
+    // this string has 256 chars
+    char *overlength = "wEgfH2Sqe8AtFcUqX6VnyvZz6A4AZtbKRvGwQWvtPLrt7aaLb6wtqFzqQ2dLYLhTwJpAuVbsRTGfjvP2kvsVSYQLLeGuPjJyYnMt5e8TqtmYuPTb78uuAx7KyQB9ce95eacs3Jp32KMNtb7BTuKjQ236MnMX3mFWYAkALcj5axpQnFaGyU3HvpYrX24FTEztuZ3zpNnqBWQyHPVa6efGTzmUXMADxjw3AbG5sTGzDca7rucsfQRAZby8ZWKm66pV";
+    ck_assert_uint_eq(UA_EndpointUrl_split(overlength, hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+
+    // check for too short url
+    ck_assert_uint_eq(UA_EndpointUrl_split("inv.ali:/", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+
+    // check for opc.tcp:// protocol
+    ck_assert_uint_eq(UA_EndpointUrl_split("inv.ali://", hostname, &port, &path), UA_STATUSCODE_BADATTRIBUTEIDINVALID);
+
+    // empty url
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(strlen(hostname), 0);
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path, NULL);
+
+    // only hostname
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path, NULL);
+
+    // empty port
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path, NULL);
+
+    // specific port
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:1234", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 1234);
+    ck_assert_ptr_eq(path, NULL);
+
+    // IPv6
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"[2001:0db8:85a3::8a2e:0370:7334]");
+    ck_assert_uint_eq(port, 1234);
+    ck_assert_str_eq(path, "path");
+
+    // empty hostname
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://:", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(strlen(hostname),0);
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path, NULL);
+
+    // empty hostname and no port
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp:///", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(strlen(hostname),0);
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path,0);
+
+    // overlength port
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:12345678", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+
+    // too high port
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:65536", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+
+    // port not a number
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:6x6", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+
+    // no port but path
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname/", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 0);
+    ck_assert_ptr_eq(path, 0);
+
+    // port and path
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:1234/path", hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 1234);
+    ck_assert_str_eq(path, "path");
+
+    // full url, but only hostname required
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:1234/path", hostname, NULL, NULL), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+
+    // full url, but only hostname and port required
+    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:1234/path", hostname, &port, NULL), UA_STATUSCODE_GOOD);
+    ck_assert_str_eq(hostname,"hostname");
+    ck_assert_uint_eq(port, 1234);
+}
+END_TEST
+
+
+START_TEST(readNumber) {
+    UA_UInt32 result;
+    ck_assert_uint_eq(UA_readNumber(NULL, 0, NULL), 0);
+
+    ck_assert_uint_eq(UA_readNumber((UA_Byte*)"x", 1, &result), 0);
+
+    ck_assert_uint_eq(UA_readNumber((UA_Byte*)"1x", 2, &result), 1);
+    ck_assert_uint_eq(result, 1);
+
+    ck_assert_uint_eq(UA_readNumber((UA_Byte*)"123456789", 9, &result), 9);
+    ck_assert_uint_eq(result, 123456789);
+}
+END_TEST
+
+static Suite* testSuite_Utils(void) {
+    Suite *s = suite_create("Utils");
+    TCase *tc_endpointUrl_split = tcase_create("EndpointUrl_split");
+    tcase_add_test(tc_endpointUrl_split, EndpointUrl_split);
+    suite_add_tcase(s,tc_endpointUrl_split);
+    TCase *tc_utils = tcase_create("Utils");
+    tcase_add_test(tc_utils, readNumber);
+    suite_add_tcase(s,tc_utils);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_Utils();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr,CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}