Przeglądaj źródła

Create specific method for splitting endpoint url. Fix #778

Windows wants strncpy_s

Port is max 6 chars

Code refactoring and cleanup

Add support for IPv6

cosmetic improvements; untabify, break overly long lines
Stefan Profanter 8 lat temu
rodzic
commit
0fdfcc43d4

+ 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

+ 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;
     }
 

+ 2 - 18
src/server/ua_services_attribute.c

@@ -5,25 +5,9 @@
 /* Read Attribute */
 /******************/
 
-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, UA_NumericRangeDimension *dim) {
-    size_t progress = readNumber(buf, buflen, &dim->min);
+    size_t progress = UA_readNumber(buf, buflen, &dim->min);
     if(progress == 0)
         return 0;
     if(buflen <= progress + 1 || buf[progress] != ':') {
@@ -32,7 +16,7 @@ readDimension(UA_Byte *buf, size_t buflen, 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;
 

+ 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_ */

+ 4 - 0
tests/CMakeLists.txt

@@ -132,3 +132,7 @@ add_test(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(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)

+ 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;
+}