Browse Source

Refactor UA_parseEndpointUrl to work with UA_String; Create ua_util.c for utility functions

Julius Pfrommer 7 years ago
parent
commit
3a74a089ae
7 changed files with 191 additions and 212 deletions
  1. 7 0
      CHANGELOG
  2. 1 0
      CMakeLists.txt
  3. 14 14
      include/ua_connection.h
  4. 12 12
      plugins/ua_network_tcp.c
  5. 0 134
      src/ua_connection.c
  6. 88 0
      src/ua_util.c
  7. 69 52
      tests/check_utils.c

+ 7 - 0
CHANGELOG

@@ -1,6 +1,13 @@
 The changelog tracks changes to the public API.
 Internal refactorings and bug fixes are not reported here.
 
+2017-04-16 jpfr <julius.pfrommer@web.de>
+
+    * Refactor UA_parseEndpointUrl to work with UA_String
+
+      The output hostname and path now point into the original
+      endpointUrl with an appropriate length.
+
 2017-04-14 jpfr <julius.pfrommer@web.de>
 
     * Auto-instantiate only child nodes marked as mandatory (fixes #1004)

+ 1 - 0
CMakeLists.txt

@@ -252,6 +252,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/ua_types_encoding_binary.c
                 ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.c
                 ${PROJECT_BINARY_DIR}/src_generated/ua_transport_generated.c
+                ${PROJECT_SOURCE_DIR}/src/ua_util.c
                 ${PROJECT_SOURCE_DIR}/src/ua_connection.c
                 ${PROJECT_SOURCE_DIR}/src/ua_securechannel.c
                 ${PROJECT_SOURCE_DIR}/src/ua_session.c

+ 14 - 14
include/ua_connection.h

@@ -108,21 +108,21 @@ 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
- */
+/* Split the given endpoint url into hostname, port and path. All arguments must
+ * be non-NULL.
+ *
+ * @param endpointUrl The endpoint URL.
+ * @param outHostname Set to the parsed hostname. The string points into the
+ *        original endpointUrl, so no memory is allocated. If an IPv6 address is
+ *        given, hostname contains e.g. '[2001:0db8:85a3::8a2e:0370:7334]'
+ * @param outPort Set to the port of the url or left unchanged.
+ * @param outPath Set to the path if one is present in the endpointUrl. The
+ *        starting (and trailing) '/' is NOT included in the path. The string
+ *        points into the original endpointUrl, so no memory is allocated.
+ * @return Returns UA_STATUSCODE_BADTCPENDPOINTURLINVALID if parsing failed. */
 UA_StatusCode UA_EXPORT
-UA_EndpointUrl_split(const char *endpointUrl, char *hostname,
-                     UA_UInt16 * port, const char ** path);
+UA_parseEndpointUrl(const UA_String *endpointUrl, UA_String *outHostname,
+                    UA_UInt16 *outPort, UA_String *outPath);
 
 #ifdef __cplusplus
 } // extern "C"

+ 12 - 12
plugins/ua_network_tcp.c

@@ -635,26 +635,26 @@ UA_ClientConnectionTCP(UA_ConnectionConfig conf, const char *endpointUrl,
     connection.releaseSendBuffer = ClientNetworkLayerReleaseBuffer;
     connection.releaseRecvBuffer = ClientNetworkLayerReleaseBuffer;
 
-    char hostname[512];
+    UA_String endpointUrlString = UA_STRING((char*)(uintptr_t)endpointUrl);
+    UA_String hostnameString = UA_STRING_NULL;
+    UA_String pathString = UA_STRING_NULL;
     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);
+    char hostname[512];
+
+    UA_StatusCode parse_retval =
+        UA_parseEndpointUrl(&endpointUrlString, &hostnameString, &port, &pathString);
+    if(parse_retval != UA_STATUSCODE_GOOD || hostnameString.length > 511) {
+        UA_LOG_WARNING(logger, UA_LOGCATEGORY_NETWORK,
+                       "Server url is invalid: %s", endpointUrl);
         return connection;
     }
-
     if(port == 0) {
         port = 4840;
         UA_LOG_INFO(logger, UA_LOGCATEGORY_NETWORK,
                     "No port defined, using standard port %d", port);
     }
+    memcpy(hostname, hostnameString.data, hostnameString.length);
+    hostname[hostnameString.length] = 0;
 
     struct addrinfo hints, *server;
     memset(&hints, 0, sizeof(hints));

+ 0 - 134
src/ua_connection.c

@@ -195,137 +195,3 @@ UA_Connection_attachSecureChannel(UA_Connection *connection, UA_SecureChannel *c
     if(UA_atomic_cmpxchg((void**)&channel->connection, NULL, connection) == NULL)
         UA_atomic_xchg((void**)&connection->channel, (void*)channel);
 }
-
-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) {
-    UA_assert(buf);
-    UA_assert(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(c < '0' || c > '9')
-            break;
-        n = (n*10) + (UA_UInt32)(c-'0');
-        ++progress;
-    }
-    *number = n;
-    return progress;
-}

+ 88 - 0
src/ua_util.c

@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_util.h"
+#include "ua_connection.h"
+
+size_t
+UA_readNumber(UA_Byte *buf, size_t buflen, UA_UInt32 *number) {
+    UA_assert(buf);
+    UA_assert(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(c < '0' || c > '9')
+            break;
+        n = (n*10) + (UA_UInt32)(c-'0');
+        ++progress;
+    }
+    *number = n;
+    return progress;
+}
+
+UA_StatusCode
+UA_parseEndpointUrl(const UA_String *endpointUrl, UA_String *outHostname,
+                    UA_UInt16 *outPort, UA_String *outPath) {
+    /* Url must begin with "opc.tcp://" */
+    if(endpointUrl->length < 11 || strncmp((char*)endpointUrl->data, "opc.tcp://", 10) != 0)
+        return UA_STATUSCODE_BADTCPENDPOINTURLINVALID;
+
+    /* Where does the hostname end? */
+    size_t pos = 10;
+    if(endpointUrl->data[pos] == '[') {
+        /* IPv6: opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path */
+        for(; pos < endpointUrl->length; ++pos) {
+            if(endpointUrl->data[pos] == ']')
+                break;
+        }
+        if(pos == endpointUrl->length)
+            return UA_STATUSCODE_BADTCPENDPOINTURLINVALID;
+        pos++;
+    } else {
+        /* IPv4 or hostname: opc.tcp://something.something:1234/path */
+        for(; pos < endpointUrl->length; ++pos) {
+            if(endpointUrl->data[pos] == ':' || endpointUrl->data[pos] == '/')
+                break;
+        }
+    }
+
+    /* Set the hostname */
+    outHostname->data = &endpointUrl->data[10];
+    outHostname->length = pos - 10;
+    if(pos == endpointUrl->length)
+        return UA_STATUSCODE_GOOD;
+
+    /* Set the port */
+    if(endpointUrl->data[pos] == ':') {
+        if(++pos == endpointUrl->length)
+            return UA_STATUSCODE_BADTCPENDPOINTURLINVALID;
+        UA_UInt32 largeNum;
+        size_t progress = UA_readNumber(&endpointUrl->data[pos], endpointUrl->length - pos, &largeNum);
+        if(progress == 0 || largeNum > 65535)
+            return UA_STATUSCODE_BADTCPENDPOINTURLINVALID;
+        /* Test if the end of a valid port was reached */
+        pos += progress;
+        if(pos == endpointUrl->length || endpointUrl->data[pos] == '/')
+            *outPort = (UA_UInt16)largeNum;
+        if(pos == endpointUrl->length)
+            return UA_STATUSCODE_GOOD;
+    }
+
+    /* Set the path */
+    UA_assert(pos < endpointUrl->length);
+    if(endpointUrl->data[pos] != '/')
+        return UA_STATUSCODE_BADTCPENDPOINTURLINVALID;
+    if(++pos == endpointUrl->length)
+        return UA_STATUSCODE_GOOD;
+    outPath->data = &endpointUrl->data[pos];
+    outPath->length = endpointUrl->length - pos;
+
+    /* Remove trailing slash from the path */
+    if(endpointUrl->data[endpointUrl->length - 1] == '/')
+        outPath->length--;
+
+    return UA_STATUSCODE_GOOD;
+}

+ 69 - 52
tests/check_utils.c

@@ -10,95 +10,112 @@
 #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);
+    UA_String hostname = UA_STRING_NULL;
+    UA_String path = UA_STRING_NULL;
+    UA_UInt16 port = 0;
 
     // check for too short url
-    ck_assert_uint_eq(UA_EndpointUrl_split("inv.ali:/", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+    UA_String endPointUrl = UA_STRING("inv.ali:/");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
 
     // check for opc.tcp:// protocol
-    ck_assert_uint_eq(UA_EndpointUrl_split("inv.ali://", hostname, &port, &path), UA_STATUSCODE_BADATTRIBUTEIDINVALID);
+    endPointUrl = UA_STRING("inv.ali://");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
 
     // empty url
-    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://", hostname, &port, &path), UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(strlen(hostname), 0);
+    endPointUrl = UA_STRING("opc.tcp://");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
+    ck_assert(UA_String_equal(&hostname, &UA_STRING_NULL));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path, NULL);
+    ck_assert(UA_String_equal(&path, &UA_STRING_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");
+    endPointUrl = UA_STRING("opc.tcp://hostname");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    UA_String expected = UA_STRING("hostname");
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path, NULL);
+    ck_assert(UA_String_equal(&path, &UA_STRING_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");
+    endPointUrl = UA_STRING("opc.tcp://hostname:");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path, NULL);
+    ck_assert(UA_String_equal(&path, &UA_STRING_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");
+    endPointUrl = UA_STRING("opc.tcp://hostname:1234");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_GOOD);
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 1234);
-    ck_assert_ptr_eq(path, NULL);
+    ck_assert(UA_String_equal(&path, &UA_STRING_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]");
+    endPointUrl = UA_STRING("opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    expected = UA_STRING("[2001:0db8:85a3::8a2e:0370:7334]");
+    UA_String expectedPath = UA_STRING("path");
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 1234);
-    ck_assert_str_eq(path, "path");
+    ck_assert(UA_String_equal(&path, &expectedPath));
 
     // empty hostname
-    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://:", hostname, &port, &path), UA_STATUSCODE_GOOD);
-    ck_assert_uint_eq(strlen(hostname),0);
+    endPointUrl = UA_STRING("opc.tcp://:");
+    port = 0;
+    path = UA_STRING_NULL;
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
+    ck_assert(UA_String_equal(&hostname, &UA_STRING_NULL));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path, NULL);
+    ck_assert(UA_String_equal(&path, &UA_STRING_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);
+    endPointUrl = UA_STRING("opc.tcp:///");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert(UA_String_equal(&hostname, &UA_STRING_NULL));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path,0);
+    ck_assert(UA_String_equal(&path, &UA_STRING_NULL));
 
     // 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);
+    endPointUrl = UA_STRING("opc.tcp://hostname:12345678");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
 
     // port not a number
-    ck_assert_uint_eq(UA_EndpointUrl_split("opc.tcp://hostname:6x6", hostname, &port, &path), UA_STATUSCODE_BADOUTOFRANGE);
+    endPointUrl = UA_STRING("opc.tcp://hostname:6x6");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path),
+                      UA_STATUSCODE_BADTCPENDPOINTURLINVALID);
+    expected = UA_STRING("hostname");
+    ck_assert(UA_String_equal(&hostname, &expected));
+    ck_assert_uint_eq(port, 0);
+    ck_assert(UA_String_equal(&path, &UA_STRING_NULL));
 
     // 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");
+    endPointUrl = UA_STRING("opc.tcp://hostname/");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 0);
-    ck_assert_ptr_eq(path, 0);
+    ck_assert(UA_String_equal(&path, &UA_STRING_NULL));
 
     // 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");
+    endPointUrl = UA_STRING("opc.tcp://hostname:1234/path");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert(UA_String_equal(&hostname, &expected));
     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");
+    ck_assert(UA_String_equal(&path, &expectedPath));
 
-    // 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");
+    // port and path with a slash
+    endPointUrl = UA_STRING("opc.tcp://hostname:1234/path/");
+    ck_assert_uint_eq(UA_parseEndpointUrl(&endPointUrl, &hostname, &port, &path), UA_STATUSCODE_GOOD);
+    ck_assert(UA_String_equal(&hostname, &expected));
     ck_assert_uint_eq(port, 1234);
+    ck_assert(UA_String_equal(&path, &expectedPath));
 }
 END_TEST