瀏覽代碼

Support OPC Foundation LDS server

Since client encryption landed into master, this was an open todo for
a long time. The multicast server example was updated to use the encryption
client.

Fixes #938
Stefan Profanter 6 年之前
父節點
當前提交
dd9696dede

+ 5 - 1
CMakeLists.txt

@@ -330,8 +330,12 @@ if(UA_ENABLE_DISCOVERY_MULTICAST)
     configure_file("deps/mdnsd/libmdnsd/mdnsd_config.h.in" "${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h")
 endif()
 
+if(UA_ENABLE_DISCOVERY)
+    include_directories(${PROJECT_SOURCE_DIR}/src/client)
+endif()
+
 include_directories(${PROJECT_SOURCE_DIR}/include
-                    ${PROJECT_SOURCE_DIR}/plugins # TODO: discovery depends on the default config
+                    ${PROJECT_SOURCE_DIR}/plugins
                     ${PROJECT_SOURCE_DIR}/deps
                     ${PROJECT_BINARY_DIR}
                     ${PROJECT_BINARY_DIR}/src_generated

+ 202 - 6
examples/discovery/server_multicast.c

@@ -10,11 +10,16 @@
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <errno.h>
 #include "open62541.h"
 
 UA_Logger logger = UA_Log_Stdout;
 UA_Boolean running = true;
 
+
+const UA_ByteString
+    UA_SECURITY_POLICY_BASIC128_URI = {56, (UA_Byte *)"http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"};
+
 static void stopHandler(int sign) {
     UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
     running = false;
@@ -94,6 +99,170 @@ serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean is
     discovery_url[serverOnNetwork->discoveryUrl.length] = 0;
 }
 
+/*
+ * Get the endpoint from the server, where we can call RegisterServer2 (or RegisterServer).
+ * This is normally the endpoint with highest supported encryption mode.
+ *
+ * @param discoveryServerUrl The discovery url from the remote server
+ * @return The endpoint description (which needs to be freed) or NULL
+ */
+static
+UA_EndpointDescription *getRegisterEndpointFromServer(const char *discoveryServerUrl) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+    UA_EndpointDescription *endpointArray = NULL;
+    size_t endpointArraySize = 0;
+    UA_StatusCode retval = UA_Client_getEndpoints(client, discoveryServerUrl,
+                                                  &endpointArraySize, &endpointArray);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_Array_delete(endpointArray, endpointArraySize,
+                        &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "GetEndpoints failed with %s", UA_StatusCode_name(retval));
+        UA_Client_delete(client);
+        return NULL;
+    }
+
+    UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER, "Server has %ld endpoints", endpointArraySize);
+    UA_EndpointDescription *foundEndpoint = NULL;
+    for (size_t i = 0; i < endpointArraySize; i++) {
+        UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER, "\tURL = %.*s, SecurityMode = %s",
+                     (int) endpointArray[i].endpointUrl.length,
+                     endpointArray[i].endpointUrl.data,
+                     endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_NONE ? "None" :
+                     endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGN ? "Sign" :
+                     endpointArray[i].securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT ? "SignAndEncrypt" :
+                     "Invalid"
+        );
+        // find the endpoint with highest supported security mode
+        if ((UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) ||
+            UA_String_equal(&endpointArray[i].securityPolicyUri, &UA_SECURITY_POLICY_BASIC128_URI)) && (
+            foundEndpoint == NULL || foundEndpoint->securityMode < endpointArray[i].securityMode))
+            foundEndpoint = &endpointArray[i];
+    }
+    UA_EndpointDescription *returnEndpoint = NULL;
+    if (foundEndpoint != NULL) {
+        returnEndpoint = UA_EndpointDescription_new();
+        UA_EndpointDescription_copy(foundEndpoint, returnEndpoint);
+    }
+    UA_Array_delete(endpointArray, endpointArraySize,
+                    &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+
+    return returnEndpoint;
+}
+
+#ifdef UA_ENABLE_ENCRYPTION
+/* loadFile parses the certificate file.
+ *
+ * @param  path               specifies the file name given in argv[]
+ * @return Returns the file content after parsing */
+static UA_ByteString loadFile(const char *const path) {
+    UA_ByteString fileContents = UA_BYTESTRING_NULL;
+    if (path == NULL)
+        return fileContents;
+
+    /* Open the file */
+    FILE *fp = fopen(path, "rb");
+    if (!fp) {
+        errno = 0; /* We read errno also from the tcp layer */
+        return fileContents;
+    }
+
+    /* Get the file length, allocate the data and read */
+    fseek(fp, 0, SEEK_END);
+    fileContents.length = (size_t) ftell(fp);
+    fileContents.data = (UA_Byte *) UA_malloc(fileContents.length * sizeof(UA_Byte));
+    if (fileContents.data) {
+        fseek(fp, 0, SEEK_SET);
+        size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
+        if (read != fileContents.length)
+            UA_ByteString_deleteMembers(&fileContents);
+    } else {
+        fileContents.length = 0;
+    }
+
+    fclose(fp);
+    return fileContents;
+}
+#endif
+
+/**
+ * Initialize a client instance which is used for calling the registerServer service.
+ * If the given endpoint has securityMode NONE, a client with default configuration
+ * is returned.
+ * If it is using SignAndEncrypt, the client certificates must be provided as a
+ * command line argument and then the client is initialized using these certificates.
+ * @param endpointRegister The remote endpoint where this server should register
+ * @param argc from the main method
+ * @param argv from the main method
+ * @return NULL or the initialized non-connected client
+ */
+static
+UA_Client *getRegisterClient(UA_EndpointDescription *endpointRegister, int argc, char **argv) {
+    if (endpointRegister->securityMode == UA_MESSAGESECURITYMODE_NONE) {
+        UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Using LDS endpoint with security None");
+        return UA_Client_new(UA_ClientConfig_default);
+    }
+#ifdef UA_ENABLE_ENCRYPTION
+    if (endpointRegister->securityMode == UA_MESSAGESECURITYMODE_SIGN) {
+        UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "LDS endpoint which only supports Sign is currently not supported");
+        return NULL;
+    }
+
+    UA_Client *clientRegister;
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Using LDS endpoint with security SignAndEncrypt");
+
+    UA_ByteString certificate = UA_BYTESTRING_NULL;
+    UA_ByteString privateKey = UA_BYTESTRING_NULL;
+    UA_ByteString *trustList = NULL;
+    size_t trustListSize = 0;
+    UA_ByteString *revocationList = NULL;
+    size_t revocationListSize = 0;
+
+
+    if (argc < 3) {
+        UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                     "The Certificate and key is missing."
+                         "The required arguments are "
+                         "<client-certificate.der> <client-private-key.der> "
+                         "[<trustlist1.crl>, ...]");
+        return NULL;
+    }
+    certificate = loadFile(argv[1]);
+    privateKey = loadFile(argv[2]);
+
+    /* Load the trustList. Load revocationList is not supported now */
+    if (argc > 3) {
+        trustListSize = (size_t) argc - 3;
+        UA_StatusCode retval = UA_ByteString_allocBuffer(trustList, trustListSize);
+        if (retval != UA_STATUSCODE_GOOD) {
+            UA_ByteString_deleteMembers(&certificate);
+            UA_ByteString_deleteMembers(&privateKey);
+            return NULL;
+        }
+
+        for (size_t trustListCount = 0; trustListCount < trustListSize; trustListCount++) {
+            trustList[trustListCount] = loadFile(argv[trustListCount + 3]);
+        }
+    }
+
+
+    /* Secure client initialization */
+    clientRegister = UA_Client_secure_new(UA_ClientConfig_default,
+                                          certificate, privateKey,
+                                          &endpointRegister->serverCertificate,
+                                          trustList, trustListSize,
+                                          revocationList, revocationListSize);
+    UA_ByteString_deleteMembers(&certificate);
+    UA_ByteString_deleteMembers(&privateKey);
+    for (size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++) {
+        UA_ByteString_deleteMembers(&trustList[deleteCount]);
+    }
+
+    return clientRegister;
+#else
+	return NULL;
+#endif
+}
+
 int main(int argc, char **argv) {
     signal(SIGINT, stopHandler); /* catches ctrl-c */
     signal(SIGTERM, stopHandler);
@@ -155,13 +324,39 @@ int main(int argc, char **argv) {
     }
     UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "LDS-ME server found on %s", discovery_url);
 
-    // periodic server register after 10 Minutes, delay first register for 500ms
-    retval = UA_Server_addPeriodicServerRegisterCallback(server, discovery_url,
+    /* Check if the server supports sign and encrypt. OPC Foundation LDS requires an encrypted session for
+     * RegisterServer call, our server currently uses encrpytion optionally */
+    UA_EndpointDescription *endpointRegister = getRegisterEndpointFromServer(discovery_url);
+    UA_free(discovery_url);
+    if (endpointRegister == NULL || endpointRegister->securityMode == UA_MESSAGESECURITYMODE_INVALID) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not find any suitable endpoints on discovery server");
+        UA_Server_delete(server);
+        UA_ServerConfig_delete(config);
+        return 1;
+    }
+
+    UA_Client *clientRegister = getRegisterClient(endpointRegister, argc, argv);
+    if (!clientRegister) {
+        UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                     "Could not create the client for remote registering");
+        UA_Server_delete(server);
+        UA_ServerConfig_delete(config);
+        return 1;
+    }
+
+    /* Connect the client */
+    char *endpointUrl = (char*)UA_malloc(endpointRegister->endpointUrl.length + 1);
+    memcpy(endpointUrl, endpointRegister->endpointUrl.data, endpointRegister->endpointUrl.length);
+    endpointUrl[endpointRegister->endpointUrl.length] = 0;
+    retval = UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, endpointUrl,
                                                          10 * 60 * 1000, 500, NULL);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
                      "Could not create periodic job for server register. StatusCode %s",
                      UA_StatusCode_name(retval));
+        UA_free(endpointUrl);
+        UA_Client_disconnect(clientRegister);
+        UA_Client_delete(clientRegister);
         UA_Server_delete(server);
         UA_ServerConfig_delete(config);
         return 1;
@@ -173,15 +368,16 @@ int main(int argc, char **argv) {
     UA_Server_run_shutdown(server);
 
     // UNregister the server from the discovery server.
-    retval = UA_Server_unregister_discovery(server, discovery_url);
-    //retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" );
-    if(retval != UA_STATUSCODE_GOOD)
+    retval = UA_Server_unregister_discovery(server, clientRegister);
+    if (retval != UA_STATUSCODE_GOOD)
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
                      "Could not unregister server from discovery server. "
                      "StatusCode %s", UA_StatusCode_name(retval));
 
+    UA_free(endpointUrl);
+    UA_Client_disconnect(clientRegister);
+    UA_Client_delete(clientRegister);
     UA_Server_delete(server);
     UA_ServerConfig_delete(config);
-    UA_free(discovery_url);
     return (int)retval;
 }

+ 9 - 2
examples/discovery/server_register.c

@@ -91,10 +91,11 @@ int main(int argc, char **argv) {
                                         myIntegerName, UA_NODEID_NULL, attr, dateDataSource,
                                         &myInteger, NULL);
 
+    UA_Client *clientRegister = UA_Client_new(UA_ClientConfig_default);
 
     // periodic server register after 10 Minutes, delay first register for 500ms
     UA_StatusCode retval =
-        UA_Server_addPeriodicServerRegisterCallback(server, DISCOVERY_SERVER_ENDPOINT,
+        UA_Server_addPeriodicServerRegisterCallback(server, clientRegister, DISCOVERY_SERVER_ENDPOINT,
                                                     10 * 60 * 1000, 500, NULL);
     // UA_StatusCode retval = UA_Server_addPeriodicServerRegisterJob(server,
     // "opc.tcp://localhost:4840", 10*60*1000, 500, NULL);
@@ -102,6 +103,8 @@ int main(int argc, char **argv) {
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
                      "Could not create periodic job for server register. StatusCode %s",
                      UA_StatusCode_name(retval));
+        UA_Client_disconnect(clientRegister);
+        UA_Client_delete(clientRegister);
         UA_Server_delete(server);
         UA_ServerConfig_delete(config);
         return (int)retval;
@@ -112,19 +115,23 @@ int main(int argc, char **argv) {
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
                      "Could not start the server. StatusCode %s",
                      UA_StatusCode_name(retval));
+        UA_Client_disconnect(clientRegister);
+        UA_Client_delete(clientRegister);
         UA_Server_delete(server);
         UA_ServerConfig_delete(config);
         return (int)retval;
     }
 
     // UNregister the server from the discovery server.
-    retval = UA_Server_unregister_discovery(server, DISCOVERY_SERVER_ENDPOINT);
+    retval = UA_Server_unregister_discovery(server, clientRegister);
     //retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" );
     if(retval != UA_STATUSCODE_GOOD)
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
                      "Could not unregister server from discovery server. StatusCode %s",
                      UA_StatusCode_name(retval));
 
+    UA_Client_disconnect(clientRegister);
+    UA_Client_delete(clientRegister);
     UA_Server_delete(server);
     UA_ServerConfig_delete(config);
     return (int)retval;

+ 8 - 0
include/ua_client.h

@@ -101,6 +101,14 @@ UA_Client_delete(UA_Client *client);
 UA_StatusCode UA_EXPORT
 UA_Client_connect(UA_Client *client, const char *endpointUrl);
 
+/* Connect to the server without creating a session
+ *
+ * @param client to use
+ * @param endpointURL to connect (for example "opc.tcp://localhost:4840")
+ * @return Indicates whether the operation succeeded or returns an error code */
+UA_StatusCode UA_EXPORT
+UA_Client_connect_noSession(UA_Client *client, const char *endpointUrl);
+
 /* Connect to the selected server with the given username and password
  *
  * @param client to use

+ 0 - 9
include/ua_client_config.h

@@ -90,15 +90,6 @@ typedef struct UA_ClientConfig {
     UA_UInt16 outStandingPublishRequests;
 } UA_ClientConfig;
 
-
-/* Get the client configuration from the configuration plugin. Used by the
- * server when it needs client functionality to register to a discovery server
- * or when the server needs to create a client for other purposes
- *
- * @return The client configuration structure */
-UA_ClientConfig UA_EXPORT
-UA_Server_getClientConfig(void);
-
 #ifdef __cplusplus
 }
 #endif

+ 12 - 7
include/ua_server.h

@@ -28,6 +28,7 @@ struct UA_Server;
 typedef struct UA_Server UA_Server;
 
 struct UA_ClientConfig;
+struct UA_Client;
 
 /**
  * .. _server:
@@ -454,20 +455,20 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
  * When the server shuts down you need to call unregister.
  *
  * @param server
- * @param discoveryServerUrl if set to NULL, the default value
- *        'opc.tcp://localhost:4840' will be used
+ * @param client the client which is used to call the RegisterServer. It must
+ *        already be connected to the correct endpoint
  * @param semaphoreFilePath optional parameter pointing to semaphore file. */
 UA_StatusCode UA_EXPORT
-UA_Server_register_discovery(UA_Server *server, const char* discoveryServerUrl,
+UA_Server_register_discovery(UA_Server *server, struct UA_Client *client,
                              const char* semaphoreFilePath);
 
 /* Unregister the given server instance from the discovery server.
  * This should only be called when the server is shutting down.
  * @param server
- * @param discoveryServerUrl if set to NULL, the default value
- *        'opc.tcp://localhost:4840' will be used */
+ * @param client the client which is used to call the RegisterServer. It must
+ *        already be connected to the correct endpoint */
 UA_StatusCode UA_EXPORT
-UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl);
+UA_Server_unregister_discovery(UA_Server *server, struct UA_Client *client);
 
  /* Adds a periodic callback to register the server with the LDS (local discovery server)
   * periodically. The interval between each register call is given as second parameter.
@@ -484,13 +485,17 @@ UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl
   * periodic callback will be removed.
   *
   * @param server
+  * @param client the client which is used to call the RegisterServer.
+  * 		It must not yet be connected and will be connected for every register call
+  * 		to the given discoveryServerUrl.
   * @param discoveryServerUrl if set to NULL, the default value
   *        'opc.tcp://localhost:4840' will be used
   * @param intervalMs
   * @param delayFirstRegisterMs
   * @param periodicCallbackId */
 UA_StatusCode UA_EXPORT
-UA_Server_addPeriodicServerRegisterCallback(UA_Server *server, const char* discoveryServerUrl,
+UA_Server_addPeriodicServerRegisterCallback(UA_Server *server, struct UA_Client *client,
+                                            const char* discoveryServerUrl,
                                             UA_UInt32 intervalMs,
                                             UA_UInt32 delayFirstRegisterMs,
                                             UA_UInt64 *periodicCallbackId);

+ 0 - 5
plugins/ua_config_default.c

@@ -649,8 +649,3 @@ const UA_ClientConfig UA_ClientConfig_default = {
 
     10 /* .outStandingPublishRequests */
 };
-
-UA_ClientConfig UA_Server_getClientConfig(void)
-{
-    return UA_ClientConfig_default;
-}

+ 28 - 10
plugins/ua_network_tcp.c

@@ -60,7 +60,6 @@
 # define WIN32_INT (int)
 # define OPTVAL_TYPE char
 # define ERR_CONNECTION_PROGRESS WSAEWOULDBLOCK
-# define UA_sleep_ms(X) Sleep(X)
 #else /* _WIN32 */
 # if defined(UA_FREERTOS)
 #  define UA_FREERTOS_HOSTNAME "10.200.4.114"
@@ -78,7 +77,6 @@ static inline int gethostname_freertos(char* name, size_t len){
 #  ifdef BYTE_ORDER
 #   undef BYTE_ORDER
 #  endif
-#  define UA_sleep_ms(X) vTaskDelay(pdMS_TO_TICKS(X))
 # else /* Not freeRTOS */
 #  define CLOSESOCKET(S) close(S)
 #  include <arpa/inet.h>
@@ -88,16 +86,8 @@ static inline int gethostname_freertos(char* name, size_t len){
 #  if defined(_WRS_KERNEL)
 #   include <hostLib.h>
 #   include <selectLib.h>
-#   define UA_sleep_ms(X)                            \
-    {                                                \
-    struct timespec timeToSleep;                     \
-      timeToSleep.tv_sec = X / 1000;                 \
-      timeToSleep.tv_nsec = 1000000 * (X % 1000);    \
-      nanosleep(&timeToSleep, NULL);                 \
-    }
 #  else /* defined(_WRS_KERNEL) */
 #   include <sys/select.h>
-#   define UA_sleep_ms(X) usleep(X * 1000)
 #  endif /* defined(_WRS_KERNEL) */
 # endif /* Not freeRTOS */
 
@@ -124,6 +114,34 @@ static inline int gethostname_freertos(char* name, size_t len){
 # endif
 #endif /* _WIN32 */
 
+#ifndef UA_sleep_ms
+# ifdef _WIN32
+#  define UA_sleep_ms(X) Sleep(X)
+# else /* _WIN32 */
+#  if defined(UA_FREERTOS)
+#   define UA_sleep_ms(X) vTaskDelay(pdMS_TO_TICKS(X))
+#  else /* Not freeRTOS */
+#   if defined(_WRS_KERNEL)
+#    include <hostLib.h>
+#    include <selectLib.h>
+#    define UA_sleep_ms(X)                            \
+     {                                                \
+     struct timespec timeToSleep;                     \
+       timeToSleep.tv_sec = X / 1000;                 \
+       timeToSleep.tv_nsec = 1000000 * (X % 1000);    \
+       nanosleep(&timeToSleep, NULL);                 \
+     }
+#   else /* defined(_WRS_KERNEL) */
+#    define UA_sleep_ms(X) usleep(X * 1000)
+#   endif /* defined(_WRS_KERNEL) */
+#  endif /* Not freeRTOS */
+# endif /* _WIN32 */
+#else /* UA_sleep_ms */
+/* With this one can define its own UA_sleep_ms using a preprocessor define.
+E.g. see unit tests. */
+void UA_sleep_ms(size_t);
+#endif
+
 /* unsigned int for windows and workaround to a glibc bug */
 /* Additionally if GNU_LIBRARY is not defined, it may be using
  * musl libc (e.g. Docker Alpine) */

+ 2 - 0
plugins/ua_securitypolicy_basic128rsa15.c

@@ -101,6 +101,8 @@ asym_verify_sp_basic128rsa15(const UA_SecurityPolicy *securityPolicy,
 
     /* Set the RSA settings */
     mbedtls_rsa_context *rsaContext = mbedtls_pk_rsa(cc->remoteCertificate.pk);
+    if (!rsaContext)
+        return UA_STATUSCODE_BADINTERNALERROR;
     mbedtls_rsa_set_padding(rsaContext, MBEDTLS_RSA_PKCS_V15, 0);
 
     /* Verify */

+ 8 - 2
src/client/ua_client_connect.c

@@ -503,8 +503,9 @@ createSession(UA_Client *client) {
     __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST],
                         &response, &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE]);
 
-    if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
-       client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
+    if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD &&
+        (client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
+         client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)) {
         UA_ByteString_copy(&response.serverNonce, &client->channel.remoteNonce);
 
         if(!UA_ByteString_equal(&response.serverCertificate,
@@ -640,6 +641,11 @@ UA_Client_connect(UA_Client *client, const char *endpointUrl) {
     return UA_Client_connectInternal(client, endpointUrl, UA_TRUE, UA_TRUE);
 }
 
+UA_StatusCode
+UA_Client_connect_noSession(UA_Client *client, const char *endpointUrl) {
+    return UA_Client_connectInternal(client, endpointUrl, UA_TRUE, UA_FALSE);
+}
+
 UA_StatusCode
 UA_Client_connect_username(UA_Client *client, const char *endpointUrl,
                            const char *username, const char *password) {

+ 5 - 30
src/server/ua_server_discovery.c

@@ -13,32 +13,9 @@
 
 static UA_StatusCode
 register_server_with_discovery_server(UA_Server *server,
-                                      const char* discoveryServerUrl,
+                                      UA_Client *client,
                                       const UA_Boolean isUnregister,
                                       const char* semaphoreFilePath) {
-    if(!discoveryServerUrl) {
-        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
-                     "No discovery server url provided");
-        return UA_STATUSCODE_BADINTERNALERROR;
-    }
-
-    /* Create the client */
-    UA_ClientConfig clientConfig = UA_Server_getClientConfig();
-    UA_Client *client = UA_Client_new(clientConfig);
-    if(!client)
-        return UA_STATUSCODE_BADOUTOFMEMORY;
-
-    /* Connect the client */
-    UA_StatusCode retval = UA_Client_connect(client, discoveryServerUrl);
-    if(retval != UA_STATUSCODE_GOOD) {
-        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_CLIENT,
-                     "Connecting to the discovery server failed with statuscode %s",
-                     UA_StatusCode_name(retval));
-        UA_Client_disconnect(client);
-        UA_Client_delete(client);
-        return retval;
-    }
-
     /* Prepare the request. Do not cleanup the request after the service call,
      * as the members are stack-allocated or point into the server config. */
     UA_RegisterServer2Request request;
@@ -132,21 +109,19 @@ register_server_with_discovery_server(UA_Server *server,
                      UA_StatusCode_name(serviceResult));
     }
 
-    UA_Client_disconnect(client);
-    UA_Client_delete(client);
     return serviceResult;
 }
 
 UA_StatusCode
-UA_Server_register_discovery(UA_Server *server, const char* discoveryServerUrl,
+UA_Server_register_discovery(UA_Server *server, UA_Client *client,
                              const char* semaphoreFilePath) {
-    return register_server_with_discovery_server(server, discoveryServerUrl,
+    return register_server_with_discovery_server(server, client,
                                                  UA_FALSE, semaphoreFilePath);
 }
 
 UA_StatusCode
-UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl) {
-    return register_server_with_discovery_server(server, discoveryServerUrl,
+UA_Server_unregister_discovery(UA_Server *server, UA_Client *client) {
+    return register_server_with_discovery_server(server, client,
                                                  UA_TRUE, NULL);
 }
 

+ 28 - 11
src/server/ua_services_discovery.c

@@ -26,6 +26,8 @@
 
 #ifdef UA_ENABLE_DISCOVERY
 
+#include "ua_client_internal.h"
+
 static UA_StatusCode
 setApplicationDescriptionFromRegisteredServer(const UA_FindServersRequest *request,
                                               UA_ApplicationDescription *target,
@@ -595,6 +597,7 @@ struct PeriodicServerRegisterCallback {
     UA_UInt32 this_interval;
     UA_UInt32 default_interval;
     UA_Boolean registered;
+    UA_Client* client;
     const char* discovery_server_url;
 };
 
@@ -620,17 +623,26 @@ periodicServerRegister(UA_Server *server, void *data) {
         server_url = cb->discovery_server_url;
     else
         server_url = "opc.tcp://localhost:4840";
-
-    /* Register
-       You can also use a semaphore file. That file must exist. When the file is
-       deleted, the server is automatically unregistered. The semaphore file has
-       to be accessible by the discovery server
-    
-       UA_StatusCode retval = UA_Server_register_discovery(server,
-       "opc.tcp://localhost:4840", "/path/to/some/file");
-    */
-    UA_StatusCode retval = UA_Server_register_discovery(server, server_url, NULL);
-
+    UA_StatusCode retval = UA_Client_connect_noSession(cb->client, server_url);
+    if (retval == UA_STATUSCODE_GOOD) {
+        /* Register
+		   You can also use a semaphore file. That file must exist. When the file is
+		   deleted, the server is automatically unregistered. The semaphore file has
+		   to be accessible by the discovery server
+
+		   UA_StatusCode retval = UA_Server_register_discovery(server,
+		   "opc.tcp://localhost:4840", "/path/to/some/file");
+		*/
+        retval = UA_Server_register_discovery(server, cb->client, NULL);
+    }
+    if (cb->client->state == UA_CLIENTSTATE_CONNECTED) {
+        UA_StatusCode retval1 = UA_Client_disconnect(cb->client);
+        if(retval1 != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "Could not disconnect client from register server. StatusCode %s",
+                         UA_StatusCode_name(retval));
+        }
+    }
     /* Registering failed */
     if(retval != UA_STATUSCODE_GOOD) {
         UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
@@ -668,6 +680,7 @@ periodicServerRegister(UA_Server *server, void *data) {
 
 UA_StatusCode
 UA_Server_addPeriodicServerRegisterCallback(UA_Server *server,
+                                            struct UA_Client *client,
                                             const char* discoveryServerUrl,
                                             UA_UInt32 intervalMs,
                                             UA_UInt32 delayFirstRegisterMs,
@@ -681,6 +694,9 @@ UA_Server_addPeriodicServerRegisterCallback(UA_Server *server,
     }
 
 
+    if (client->connection.state != UA_CONNECTION_CLOSED)
+        return UA_STATUSCODE_BADINVALIDSTATE;
+
     /* check if we are already registering with the given discovery url and remove the old periodic call */
     {
         periodicServerRegisterCallback_entry *rs, *rs_tmp;
@@ -710,6 +726,7 @@ UA_Server_addPeriodicServerRegisterCallback(UA_Server *server,
     cb->this_interval = 500;
     cb->default_interval = intervalMs;
     cb->registered = false;
+    cb->client = client;
     cb->discovery_server_url = discoveryServerUrl;
 
 

+ 2 - 0
tests/CMakeLists.txt

@@ -16,6 +16,8 @@ include_directories(${PROJECT_SOURCE_DIR}/plugins)
 include_directories(${PROJECT_BINARY_DIR}/src_generated)
 include_directories(${PROJECT_SOURCE_DIR}/tests/testing-plugins)
 
+add_definitions(-DUA_sleep_ms=UA_comboSleep)
+
 #############################
 # Compiled binaries folders #
 #############################

+ 30 - 12
tests/server/check_discovery.c

@@ -38,6 +38,7 @@ UA_Server *server_lds;
 UA_ServerConfig *config_lds;
 UA_Boolean *running_lds;
 THREAD_HANDLE server_thread_lds;
+UA_Client *clientRegisterRepeated;
 
 THREAD_CALLBACK(serverloop_lds) {
     while(*running_lds)
@@ -121,16 +122,26 @@ static void teardown_register(void) {
 }
 
 START_TEST(Server_register) {
-    UA_StatusCode retval =
-        UA_Server_register_discovery(server_register, "opc.tcp://localhost:4840", NULL);
+    UA_Client *clientRegister = UA_Client_new(UA_ClientConfig_default);
+    ck_assert(clientRegister != NULL);
+    UA_StatusCode retval = UA_Client_connect_noSession(clientRegister, "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    retval = UA_Server_register_discovery(server_register, clientRegister , NULL);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Client_disconnect(clientRegister);
+    UA_Client_delete(clientRegister);
 }
 END_TEST
 
 START_TEST(Server_unregister) {
-    UA_StatusCode retval =
-        UA_Server_unregister_discovery(server_register, "opc.tcp://localhost:4840");
+    UA_Client *clientRegister = UA_Client_new(UA_ClientConfig_default);
+    ck_assert(clientRegister != NULL);
+    UA_StatusCode retval = UA_Client_connect_noSession(clientRegister, "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    retval = UA_Server_unregister_discovery(server_register, clientRegister);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Client_disconnect(clientRegister);
+    UA_Client_delete(clientRegister);
 }
 END_TEST
 
@@ -152,11 +163,14 @@ START_TEST(Server_register_semaphore) {
     ck_assert_ptr_ne(fp, NULL);
     fclose(fp);
 #endif
-
-    UA_StatusCode retval =
-        UA_Server_register_discovery(server_register, "opc.tcp://localhost:4840",
-                                     SEMAPHORE_PATH);
+    UA_Client *clientRegister = UA_Client_new(UA_ClientConfig_default);
+    ck_assert(clientRegister != NULL);
+    UA_StatusCode retval = UA_Client_connect_noSession(clientRegister, "opc.tcp://localhost:4840");
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    retval = UA_Server_register_discovery(server_register, clientRegister, SEMAPHORE_PATH);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Client_disconnect(clientRegister);
+    UA_Client_delete(clientRegister);
 }
 END_TEST
 
@@ -167,9 +181,11 @@ START_TEST(Server_unregister_semaphore) {
 END_TEST
 
 START_TEST(Server_register_periodic) {
+    ck_assert(clientRegisterRepeated == NULL);
+    clientRegisterRepeated = UA_Client_new(UA_ClientConfig_default);
+    ck_assert(clientRegisterRepeated != NULL);
     // periodic register every minute, first register immediately
-    UA_StatusCode retval =
-        UA_Server_addPeriodicServerRegisterCallback(server_register, "opc.tcp://localhost:4840",
+    UA_StatusCode retval = UA_Server_addPeriodicServerRegisterCallback(server_register, clientRegisterRepeated, "opc.tcp://localhost:4840",
                                                     60*1000, 100, &periodicRegisterCallbackId);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 }
@@ -180,9 +196,11 @@ START_TEST(Server_unregister_periodic) {
     UA_fakeSleep(1000);
     UA_realSleep(1000);
     UA_Server_removeRepeatedCallback(server_register, periodicRegisterCallbackId);
-    UA_StatusCode retval = UA_Server_unregister_discovery(server_register,
-                                                          "opc.tcp://localhost:4840");
+    UA_StatusCode retval = UA_Server_unregister_discovery(server_register, clientRegisterRepeated);
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_Client_disconnect(clientRegisterRepeated);
+    UA_Client_delete(clientRegisterRepeated);
+    clientRegisterRepeated=NULL;
 }
 END_TEST
 

+ 6 - 0
tests/testing-plugins/testing_clock.c

@@ -57,3 +57,9 @@ UA_realSleep(UA_UInt32 duration) {
     nanosleep(&sleepValue, NULL);
 #endif
 }
+
+void
+UA_comboSleep(UA_UInt32 duration) {
+    UA_fakeSleep(duration);
+    UA_realSleep(duration);
+}

+ 6 - 2
tests/testing-plugins/testing_clock.h

@@ -21,8 +21,12 @@
 /* Forwards the testing clock by the given duration in ms */
 void UA_fakeSleep(UA_UInt32 duration);
 
-/* Sleep for the duration in milliseconds. Used to wait for workers to complete.
- * Does not do anything in single-threaded mode. */
+/* Sleep for the duration in milliseconds. Used to wait for workers to complete. */
 void UA_realSleep(UA_UInt32 duration);
 
+/* Sleep for the duration in milliseconds and update the current time.
+ * combines fakeSleep and realSleep.
+ * */
+void UA_comboSleep(UA_UInt32 duration);
+
 #endif /* TESTING_CLOCK_H_ */