Browse Source

Service RegisterServer and Discovery (#687)

* New RegisterServer service

Add support for Discovery Server (LDS/GDS) by providing RegisterServer service.
FindServers service is updated to return the registered servers.

* Add examples for discovery server

* Use compile switch for LDS

* Use periodic jobs for server register

* Add lastSeen for registered servers and fix compilation errors

* Add cleanup of stale registrations

* Fix UA_string output with printf (use `%.*s`)

* Use correct port number
Stefan Profanter 8 years ago
parent
commit
e9e68a0ba2

+ 13 - 0
CMakeLists.txt

@@ -107,6 +107,7 @@ option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
 option(UA_ENABLE_MULTITHREADING "Enable multithreading" OFF)
+option(UA_ENABLE_DISCOVERY "Enable Discovery Service (LDS)" ON)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 if(UA_ENABLE_COVERAGE)
@@ -459,6 +460,18 @@ if(UA_BUILD_EXAMPLES)
     add_executable(server_repeated_job ${PROJECT_SOURCE_DIR}/examples/server_repeated_job.c $<TARGET_OBJECTS:open62541-object>)
     target_link_libraries(server_repeated_job ${LIBS})
 
+    if(UA_ENABLE_DISCOVERY)
+        add_executable(discovery_server_discovery ${PROJECT_SOURCE_DIR}/examples/discovery/server_discovery.c $<TARGET_OBJECTS:open62541-object>)
+        target_link_libraries(discovery_server_discovery ${LIBS})
+
+        # can currently only be build on linux, because windows is missing pthread support
+        add_executable(discovery_server_register ${PROJECT_SOURCE_DIR}/examples/discovery/server_register.c $<TARGET_OBJECTS:open62541-object>)
+        target_link_libraries(discovery_server_register ${LIBS})
+
+        add_executable(discovery_client_find_servers ${PROJECT_SOURCE_DIR}/examples/discovery/client_find_servers.c $<TARGET_OBJECTS:open62541-object>)
+        target_link_libraries(discovery_client_find_servers ${LIBS})
+    endif()
+
     add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
                       PRE_BUILD
                       COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/generate_open62541CCode.py

+ 12 - 0
examples/CMakeLists.txt

@@ -32,6 +32,18 @@ target_link_libraries(server_firstSteps ${LIBS})
 add_executable(client_firstSteps client_firstSteps.c)
 target_link_libraries(client_firstSteps ${LIBS})
 
+if(UA_ENABLE_DISCOVERY)
+	add_executable(discovery_server_discovery discovery/server_discovery.c)
+	target_link_libraries(discovery_server_discovery ${LIBS})
+
+	# can currently only be build on linux, because windows is missing pthread support
+	add_executable(discovery_server_register discovery/server_register.c)
+	target_link_libraries(discovery_server_register ${LIBS})
+
+	add_executable(discovery_client_find_servers discovery/client_find_servers.c)
+	target_link_libraries(discovery_client_find_servers ${LIBS})
+endif()
+
 if(NOT UA_ENABLE_AMALGAMATION)
 	add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
 		               PRE_BUILD

+ 203 - 0
examples/discovery/client_find_servers.c

@@ -0,0 +1,203 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+/**
+ * This client requests all the available servers from the discovery server (see server_discovery.c)
+ * and then calls GetEndpoints on the returned list of servers.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#ifdef UA_NO_AMALGAMATION
+# include "ua_client.h"
+# include "ua_config_standard.h"
+# include "ua_log_stdout.h"
+#else
+# include "open62541.h"
+#endif
+
+UA_Logger logger = UA_Log_Stdout;
+
+static UA_StatusCode FindServers(const char* discoveryServerUrl, size_t* registeredServerSize, UA_ApplicationDescription** registeredServers) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+    UA_StatusCode retval = UA_Client_connect(client, discoveryServerUrl);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_Client_delete(client);
+        return retval;
+    }
+    
+
+    UA_FindServersRequest request;
+    UA_FindServersRequest_init(&request);
+
+    /*
+     * If you want to find specific servers, you can also include the server URIs in the request:
+     *
+     */
+    //request.serverUrisSize = 1;
+    //request.serverUris = UA_malloc(sizeof(UA_String));
+    //request.serverUris[0] = UA_String_fromChars("open62541.example.server_register");
+
+    //request.localeIdsSize = 1;
+    //request.localeIds = UA_malloc(sizeof(UA_String));
+    //request.localeIds[0] = UA_String_fromChars("en");
+
+    // now send the request
+    UA_FindServersResponse response;
+    UA_FindServersResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE]);
+
+    //UA_free(request.serverUris);
+    //UA_free(request.localeIds);
+
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_CLIENT,
+                     "FindServers failed with statuscode 0x%08x", response.responseHeader.serviceResult);
+        UA_FindServersResponse_deleteMembers(&response);
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return response.responseHeader.serviceResult;
+    }
+
+    *registeredServerSize = response.serversSize;
+    *registeredServers = (UA_ApplicationDescription*)UA_Array_new(response.serversSize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
+    for(size_t i=0;i<response.serversSize;i++)
+        UA_ApplicationDescription_copy(&response.servers[i], &(*registeredServers)[i]);
+    UA_FindServersResponse_deleteMembers(&response);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+    return (int) UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode GetEndpoints(UA_Client *client, const UA_String* endpointUrl, size_t* endpointDescriptionsSize, UA_EndpointDescription** endpointDescriptions) {
+    UA_GetEndpointsRequest request;
+    UA_GetEndpointsRequest_init(&request);
+    //request.requestHeader.authenticationToken = client->authenticationToken;
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+    request.endpointUrl = *endpointUrl; // assume the endpointurl outlives the service call
+
+    UA_GetEndpointsResponse response;
+    UA_GetEndpointsResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST],
+    &response, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE]);
+
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_CLIENT,
+                     "GetEndpointRequest failed with statuscode 0x%08x", response.responseHeader.serviceResult);
+        UA_GetEndpointsResponse_deleteMembers(&response);
+        return response.responseHeader.serviceResult;
+    }
+
+    *endpointDescriptionsSize = response.endpointsSize;
+    *endpointDescriptions = (UA_EndpointDescription*)UA_Array_new(response.endpointsSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+    for(size_t i=0;i<response.endpointsSize;i++) {
+        UA_EndpointDescription_init(&(*endpointDescriptions)[i]);
+        UA_EndpointDescription_copy(&response.endpoints[i], &(*endpointDescriptions)[i]);
+    }
+    UA_GetEndpointsResponse_deleteMembers(&response);
+    return UA_STATUSCODE_GOOD;
+}
+
+int main(void) {
+
+    UA_ApplicationDescription* applicationDescriptionArray = NULL;
+    size_t applicationDescriptionArraySize = 0;
+
+    UA_StatusCode retval = FindServers("opc.tcp://localhost:4840", &applicationDescriptionArraySize, &applicationDescriptionArray);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not call FindServers service. Is the discovery server started? StatusCode 0x%08x", retval);
+        return (int)retval;
+    }
+
+    // output all the returned/registered servers
+    for (size_t i=0; i<applicationDescriptionArraySize; i++) {
+        UA_ApplicationDescription *description = &applicationDescriptionArray[i];
+        printf("Server[%lu]: %.*s", (unsigned long)i, (int)description->applicationUri.length, description->applicationUri.data);
+        printf("\n\tName: %.*s", (int)description->applicationName.text.length, description->applicationName.text.data);
+        printf("\n\tProduct URI: %.*s", (int)description->productUri.length, description->productUri.data);
+        printf("\n\tType: ");
+        switch(description->applicationType) {
+            case UA_APPLICATIONTYPE_SERVER:
+                printf("Server");
+                break;
+            case UA_APPLICATIONTYPE_CLIENT:
+                printf("Client");
+                break;
+            case UA_APPLICATIONTYPE_CLIENTANDSERVER:
+                printf("Client and Server");
+                break;
+            case UA_APPLICATIONTYPE_DISCOVERYSERVER:
+                printf("Discovery Server");
+                break;
+            default:
+                printf("Unknown");
+        }
+        printf("\n\tDiscovery URLs:");
+        for (size_t j=0; j<description->discoveryUrlsSize; j++) {
+            printf("\n\t\t[%lu]: %.*s", (unsigned long)j, (int)description->discoveryUrls[j].length, description->discoveryUrls[j].data);
+        }
+        printf("\n\n");
+    }
+
+
+    /*
+     * Now that we have the list of available servers, call get endpoints on all of them
+     */
+
+    printf("-------- Server Endpoints --------\n");
+
+    for (size_t i=0; i<applicationDescriptionArraySize; i++) {
+        UA_ApplicationDescription *description = &applicationDescriptionArray[i];
+        if (description->discoveryUrlsSize == 0) {
+            UA_LOG_INFO(logger, UA_LOGCATEGORY_CLIENT, "[GetEndpoints] Server %.*s did not provide any discovery urls. Skipping.", description->applicationUri);
+            continue;
+        }
+
+        printf("\nEndpoints for Server[%lu]: %.*s", (unsigned long)i, (int)description->applicationUri.length, description->applicationUri.data);
+
+        UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+
+        char* discoveryUrl = malloc(sizeof(char)*description->discoveryUrls[0].length+1);
+        memcpy( discoveryUrl, description->discoveryUrls[0].data, description->discoveryUrls[0].length );
+        discoveryUrl[description->discoveryUrls[0].length] = '\0';
+
+        retval = UA_Client_connect(client, discoveryUrl);
+        free(discoveryUrl);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_Client_delete(client);
+            return (int)retval;
+        }
+
+        UA_EndpointDescription* endpointArray = NULL;
+        size_t endpointArraySize = 0;
+        retval = GetEndpoints(client, &description->discoveryUrls[0], &endpointArraySize, &endpointArray);
+        if(retval != UA_STATUSCODE_GOOD) {
+            UA_Client_disconnect(client);
+            UA_Client_delete(client);
+            break;
+        }
+
+        for(size_t j = 0; j < endpointArraySize; j++) {
+            UA_EndpointDescription* endpoint = &endpointArray[j];
+            printf("\n\tEndpoint[%lu]:",(unsigned long)j);
+            printf("\n\t\tEndpoint URL: %.*s", (int)endpoint->endpointUrl.length, endpoint->endpointUrl.data);
+            printf("\n\t\tTransport profile URI: %.*s", (int)endpoint->transportProfileUri.length, endpoint->transportProfileUri.data);
+        }
+
+        UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+
+
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+    }
+
+    printf("\n");
+
+    UA_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
+
+    return (int) UA_STATUSCODE_GOOD;
+}

+ 42 - 0
examples/discovery/server_discovery.c

@@ -0,0 +1,42 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+/*
+ * Server representing a local discovery server as a central instance.
+ * Any other server can register with this server (see server_register.c). Clients can then call the
+ * find servers service to get all registered servers (see client_find_servers.c).
+ */
+
+#include <stdio.h>
+#include <signal.h>
+
+#ifdef UA_NO_AMALGAMATION
+# include "ua_types.h"
+# include "ua_server.h"
+# include "ua_config_standard.h"
+# include "ua_network_tcp.h"
+#else
+# include "open62541.h"
+#endif
+
+UA_Boolean running = true;
+static void stopHandler(int sig) {
+    running = false;
+}
+
+int main(void) {
+    signal(SIGINT,  stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    config.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
+    config.applicationDescription.applicationUri = UA_String_fromChars("open62541.example.local_discovery_server");
+    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 4048);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    return (int)retval;
+}

+ 190 - 0
examples/discovery/server_register.c

@@ -0,0 +1,190 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+/*
+ * A simple server instance which registers with the discovery server (see server_discovery.c).
+ * Before shutdown it has to unregister itself.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _MSC_VER
+# include <io.h> //access
+#else
+# include <unistd.h> //access
+#endif
+
+
+#ifdef UA_NO_AMALGAMATION
+# include "ua_types.h"
+# include "ua_server.h"
+# include "ua_config_standard.h"
+# include "ua_network_tcp.h"
+# include "ua_log_stdout.h"
+#else
+# include "open62541.h"
+#endif
+
+
+UA_Boolean running = true;
+UA_Logger logger = UA_Log_Stdout;
+
+static void stopHandler(int sign) {
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = false;
+}
+
+static UA_StatusCode
+readInteger(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
+            const UA_NumericRange *range, UA_DataValue *dataValue) {
+    dataValue->hasValue = true;
+    UA_Variant_setScalarCopy(&dataValue->value, (UA_UInt32*)handle, &UA_TYPES[UA_TYPES_INT32]);
+    // we know the nodeid is a string
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node read %s",
+                nodeid.identifier.string.length, nodeid.identifier.string.data);
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "read value %i", *(UA_UInt32*)handle);
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+writeInteger(void *handle, const UA_NodeId nodeid,
+             const UA_Variant *data, const UA_NumericRange *range) {
+    if(UA_Variant_isScalar(data) && data->type == &UA_TYPES[UA_TYPES_INT32] && data->data){
+        *(UA_UInt32*)handle = *(UA_UInt32*)data->data;
+    }
+    // we know the nodeid is a string
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node written %.*s",
+                nodeid.identifier.string.length, nodeid.identifier.string.data);
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "written value %i", *(UA_UInt32*)handle);
+    return UA_STATUSCODE_GOOD;
+}
+
+struct PeriodicServerRegisterJob {
+    UA_Guid job_id;
+    UA_Job *job;
+    UA_UInt32 this_interval;
+};
+
+/**
+ * Called by the UA_Server job.
+ * The OPC UA specification says:
+ *
+ * > If an error occurs during registration (e.g. the Discovery Server is not running) then the Server
+ * > must periodically re-attempt registration. The frequency of these attempts should start at 1 second
+ * > but gradually increase until the registration frequency is the same as what it would be if not
+ * > errors occurred. The recommended approach would double the period each attempt until reaching the maximum.
+ *
+ * We will do so by using the additional data parameter. If it is NULL, it is the first attempt
+ * (or the default periodic register of 10 Minutes).
+ * Otherwise it indicates the wait time in seconds for the next try.
+ */
+static void periodicServerRegister(UA_Server *server, void *data) {
+
+    struct PeriodicServerRegisterJob *retryJob = NULL;
+
+    // retry registration by doubling the interval. If it is again 10 Minutes, don't retry.
+    UA_UInt32 nextInterval = 0;
+
+    if (data) {
+        // if data!=NULL this method call was a retry not within the default 10 minutes.
+        retryJob = (struct PeriodicServerRegisterJob *)data;
+        // remove the retry job because we don't want to fire it again. If it still fails,
+        // we double the interval and create a new job
+        UA_Server_removeRepeatedJob(server, retryJob->job_id);
+        nextInterval = retryJob->this_interval * 2;
+        free(retryJob->job);
+        free(retryJob);
+    }
+
+
+    UA_StatusCode retval = UA_Server_register_discovery(server, "opc.tcp://localhost:4840", NULL);
+    // 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");
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not register server with discovery server. Is the discovery server started? StatusCode 0x%08x", retval);
+
+        // first retry in 1 second
+        if (nextInterval == 0)
+            nextInterval = 1;
+
+        // as long as next retry is smaller than 10 minutes, retry
+        if (nextInterval < 10*60) {
+            UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Retrying registration in %d seconds", nextInterval);
+            struct PeriodicServerRegisterJob *newRetryJob = malloc(sizeof(struct PeriodicServerRegisterJob));
+            newRetryJob->job = malloc(sizeof(UA_Job));
+            newRetryJob->this_interval = nextInterval;
+
+            newRetryJob->job->type = UA_JOBTYPE_METHODCALL;
+            newRetryJob->job->job.methodCall.method = periodicServerRegister;
+            newRetryJob->job->job.methodCall.data = newRetryJob;
+
+            UA_Server_addRepeatedJob(server, *newRetryJob->job, nextInterval*1000, &newRetryJob->job_id);
+        }
+    } else {
+        UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Server successfully registered. Next periodical register will be in 10 Minutes");
+    }
+}
+
+
+int main(int argc, char** argv) {
+    signal(SIGINT, stopHandler); /* catches ctrl-c */
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    config.applicationDescription.applicationUri=UA_String_fromChars("open62541.example.server_register");
+    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+
+    /* add a variable node to the address space */
+    UA_Int32 myInteger = 42;
+    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
+    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
+    UA_DataSource dateDataSource = (UA_DataSource) {
+            .handle = &myInteger, .read = readInteger, .write = writeInteger};
+    UA_VariableAttributes attr;
+    UA_VariableAttributes_init(&attr);
+    attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
+    attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
+
+    UA_Server_addDataSourceVariableNode(server, myIntegerNodeId,
+                                        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                        myIntegerName, UA_NODEID_NULL, attr, dateDataSource, NULL);
+
+
+    // registering the server should be done periodically. Approx. every 10 minutes. The first call will be in 10 Minutes.
+    UA_Job job = {.type = UA_JOBTYPE_METHODCALL,
+            .job.methodCall = {.method = periodicServerRegister, .data = NULL} };
+    UA_Server_addRepeatedJob(server, job, 10*60*1000, NULL);
+
+    // Register the server with the discovery server.
+    // Delay this first registration until the server is fully initialized
+    // will be freed in the callback
+    struct PeriodicServerRegisterJob *newRetryJob = malloc(sizeof(struct PeriodicServerRegisterJob));
+    newRetryJob->job = malloc(sizeof(UA_Job));
+    newRetryJob->this_interval = 0;
+    newRetryJob->job->type = UA_JOBTYPE_METHODCALL;
+    newRetryJob->job->job.methodCall.method = periodicServerRegister;
+    newRetryJob->job->job.methodCall.data = newRetryJob;
+    UA_Server_addRepeatedJob(server, *newRetryJob->job, 1000, &newRetryJob->job_id);
+
+
+    UA_StatusCode retval = UA_Server_run(server, &running);
+
+    // UNregister the server from the discovery server.
+    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 0x%08x", retval);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        return (int)retval;
+    }
+
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+
+    return (int)retval;
+}

+ 1 - 0
include/ua_config.h.in

@@ -32,6 +32,7 @@ extern "C" {
 #cmakedefine UA_ENABLE_MULTITHREADING
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
+#cmakedefine UA_ENABLE_DISCOVERY
 #cmakedefine UA_ENABLE_TYPENAMES
 #cmakedefine UA_ENABLE_GENERATE_NAMESPACE0
 #cmakedefine UA_ENABLE_EXTERNAL_NAMESPACES

+ 25 - 0
include/ua_server.h

@@ -430,6 +430,31 @@ UA_StatusCode UA_EXPORT
 UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
                                UA_NodeIteratorCallback callback, void *handle);
 
+#ifdef UA_ENABLE_DISCOVERY
+ /**
+ * Discovery
+ * --------- */
+
+ /*
+  * Register the given server instance at the discovery server.
+  * This should be called periodically.
+  * The semaphoreFilePath is optional. If the given file is deleted,
+  * the server will automatically be unregistered. This could be
+  * for example a pid file which is deleted if the server crashes.
+  *
+  * When the server shuts down you need to call unregister.
+  */
+ UA_StatusCode UA_EXPORT
+ UA_Server_register_discovery(UA_Server *server, const char* discoveryServerUrl, const char* semaphoreFilePath);
+
+ /**
+  * Unregister the given server instance from the discovery server.
+  * This should only be called when the server is shutting down.
+  */
+ UA_StatusCode UA_EXPORT
+ UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl);
+#endif
+
 /**
  * Method Call
  * ----------- */

+ 121 - 0
src/server/ua_server.c

@@ -6,6 +6,11 @@
 #include "ua_services.h"
 #include "ua_nodeids.h"
 
+#ifdef UA_ENABLE_DISCOVERY
+#include "ua_client.h"
+#include "ua_config_standard.h"
+#endif
+
 #ifdef UA_ENABLE_GENERATE_NAMESPACE0
 #include "ua_namespaceinit_generated.h"
 #endif
@@ -264,6 +269,15 @@ void UA_Server_delete(UA_Server *server) {
     UA_Array_delete(server->endpointDescriptions, server->endpointDescriptionsSize,
                     &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
 
+#ifdef UA_ENABLE_DISCOVERY
+    registeredServer_list_entry *current, *temp;
+    LIST_FOREACH_SAFE(current, &server->registeredServers, pointers, temp) {
+        LIST_REMOVE(current, pointers);
+        UA_RegisteredServer_deleteMembers(&current->registeredServer);
+        UA_free(current);
+    }
+#endif
+
 #ifdef UA_ENABLE_MULTITHREADING
     pthread_cond_destroy(&server->dispatchQueue_condition);
 #endif
@@ -275,6 +289,9 @@ static void UA_Server_cleanup(UA_Server *server, void *_) {
     UA_DateTime now = UA_DateTime_now();
     UA_SessionManager_cleanupTimedOut(&server->sessionManager, now);
     UA_SecureChannelManager_cleanupTimedOut(&server->secureChannelManager, now);
+#ifdef UA_ENABLE_DISCOVERY
+    UA_Discovery_cleanupTimedOut(server, now);
+#endif
 }
 
 static UA_StatusCode
@@ -554,6 +571,12 @@ UA_Server * UA_Server_new(const UA_ServerConfig config) {
                       .job.methodCall = {.method = UA_Server_cleanup, .data = NULL} };
     UA_Server_addRepeatedJob(server, cleanup, 10000, NULL);
 
+#ifdef UA_ENABLE_DISCOVERY
+    // Discovery service
+    LIST_INIT(&server->registeredServers);
+    server->registeredServersSize = 0;
+#endif
+
     server->startTime = UA_DateTime_now();
 
     /**************/
@@ -1490,3 +1513,101 @@ UA_CallMethodResult UA_Server_call(UA_Server *server, const UA_CallMethodRequest
     return result;
 }
 #endif
+
+#ifdef UA_ENABLE_DISCOVERY
+static UA_StatusCode register_server_with_discovery_server(UA_Server *server, const char* discoveryServerUrl, const UA_Boolean isUnregister, const char* semaphoreFilePath) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+    UA_StatusCode retval = UA_Client_connect(client, discoveryServerUrl);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_Client_delete(client);
+        return retval;
+    }
+
+    UA_RegisterServerRequest request;
+    UA_RegisterServerRequest_init(&request);
+
+    request.requestHeader.timestamp = UA_DateTime_now();
+    request.requestHeader.timeoutHint = 10000;
+
+    request.server.isOnline = !isUnregister;
+
+    // copy all the required data from applicationDescription to request
+    retval |= UA_String_copy(&server->config.applicationDescription.applicationUri, &request.server.serverUri);
+    retval |= UA_String_copy(&server->config.applicationDescription.productUri, &request.server.productUri);
+
+    request.server.serverNamesSize = 1;
+    request.server.serverNames = UA_malloc(sizeof(UA_LocalizedText));
+    if (!request.server.serverNames) {
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    retval |= UA_LocalizedText_copy(&server->config.applicationDescription.applicationName, &request.server.serverNames[0]);
+    
+    request.server.serverType = server->config.applicationDescription.applicationType;
+    retval |= UA_String_copy(&server->config.applicationDescription.gatewayServerUri, &request.server.gatewayServerUri);
+    // TODO where do we get the discoveryProfileUri for application data?
+
+    request.server.discoveryUrls = UA_malloc(sizeof(UA_String) * server->config.applicationDescription.discoveryUrlsSize);
+    if (!request.server.serverNames) {
+        UA_RegisteredServer_deleteMembers(&request.server);
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    for (size_t i = 0; i<server->config.applicationDescription.discoveryUrlsSize; i++) {
+        retval |= UA_String_copy(&server->config.applicationDescription.discoveryUrls[i], &request.server.discoveryUrls[i]);
+    }
+
+    /* add the discoveryUrls from the networklayers */
+    UA_String *disc = UA_realloc(request.server.discoveryUrls, sizeof(UA_String) *
+                                                                           (request.server.discoveryUrlsSize + server->config.networkLayersSize));
+    if(!disc) {
+        UA_RegisteredServer_deleteMembers(&request.server);
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    size_t existing = request.server.discoveryUrlsSize;
+    request.server.discoveryUrls = disc;
+    request.server.discoveryUrlsSize += server->config.networkLayersSize;
+
+    // TODO: Add nl only if discoveryUrl not already present
+    for(size_t i = 0; i < server->config.networkLayersSize; i++) {
+        UA_ServerNetworkLayer *nl = &server->config.networkLayers[i];
+        UA_String_copy(&nl->discoveryUrl, &request.server.discoveryUrls[existing + i]);
+    }
+
+    if (semaphoreFilePath) {
+        request.server.semaphoreFilePath = UA_String_fromChars(semaphoreFilePath);
+    }
+
+    // now send the request
+    UA_RegisterServerResponse response;
+    UA_RegisterServerResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE]);
+
+    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                     "RegisterServer failed with statuscode 0x%08x", response.responseHeader.serviceResult);
+        UA_RegisterServerResponse_deleteMembers(&response);
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return response.responseHeader.serviceResult;
+    }
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode UA_Server_register_discovery(UA_Server *server, const char* discoveryServerUrl, const char* semaphoreFilePath) {
+    return register_server_with_discovery_server(server, discoveryServerUrl, UA_FALSE, semaphoreFilePath);
+}
+
+UA_StatusCode UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl) {
+    return register_server_with_discovery_server(server, discoveryServerUrl, UA_TRUE, NULL);
+}
+#endif

+ 7 - 0
src/server/ua_server_binary.c

@@ -76,6 +76,13 @@ getServicePointers(UA_UInt32 requestTypeId, const UA_DataType **requestType,
         *responseType = &UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE];
         *requiresSession = false;
         break;
+#ifdef UA_ENABLE_DISCOVERY
+    case UA_NS0ID_REGISTERSERVERREQUEST:
+        *service = (UA_Service)Service_RegisterServer;
+        *requestType = &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST];
+        *responseType = &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE];
+        break;
+#endif
     case UA_NS0ID_CREATESESSIONREQUEST:
         *service = (UA_Service)Service_CreateSession;
         *requestType = &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST];

+ 14 - 0
src/server/ua_server_internal.h

@@ -38,6 +38,14 @@ typedef struct {
 extern UA_THREAD_LOCAL UA_Session* methodCallSession;
 #endif
 
+#ifdef UA_ENABLE_DISCOVERY
+typedef struct registeredServer_list_entry {
+    LIST_ENTRY(registeredServer_list_entry) pointers;
+    UA_RegisteredServer registeredServer;
+    UA_DateTime lastSeen;
+} registeredServer_list_entry;
+#endif
+
 struct UA_Server {
     /* Meta */
     UA_DateTime startTime;
@@ -51,6 +59,12 @@ struct UA_Server {
     /* Address Space */
     UA_NodeStore *nodestore;
 
+#ifdef UA_ENABLE_DISCOVERY
+    /* Discovery */
+    LIST_HEAD(registeredServer_list, registeredServer_list_entry) registeredServers; // doubly-linked list of registered servers
+    size_t registeredServersSize;
+#endif
+
     size_t namespacesSize;
     UA_String *namespaces;
 

+ 8 - 1
src/server/ua_services.h

@@ -38,7 +38,14 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session,
                           const UA_GetEndpointsRequest *request,
                           UA_GetEndpointsResponse *response);
 
-/* Not Implemented: Service_RegisterServer */
+#ifdef UA_ENABLE_DISCOVERY
+/* Registers a remote server in the local discovery service. */
+void Service_RegisterServer(UA_Server *server, UA_Session *session,
+							const UA_RegisterServerRequest *request,
+							UA_RegisterServerResponse *response);
+
+void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now);
+#endif
 
 /**
  * SecureChannel Service Set

+ 318 - 28
src/server/ua_services_discovery.c

@@ -1,43 +1,203 @@
 #include "ua_server_internal.h"
 #include "ua_services.h"
-#include "ua_util.h"
+
+
+#ifdef UA_ENABLE_DISCOVERY
+    #ifdef _MSC_VER
+    # include <io.h> //access
+    #else
+    # include <unistd.h> //access
+	#endif
+#endif
+
+#ifdef UA_ENABLE_DISCOVERY
+static UA_StatusCode copyRegisteredServerToApplicationDescription(const UA_FindServersRequest *request, UA_ApplicationDescription *target, const UA_RegisteredServer* registeredServer) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+
+    UA_ApplicationDescription_init(target);
+
+    retval |= UA_String_copy(&registeredServer->serverUri, &target->applicationUri);
+    retval |= UA_String_copy(&registeredServer->productUri, &target->productUri);
+
+    // if the client requests a specific locale, select the corresponding server name
+    if (request->localeIdsSize) {
+        UA_Boolean appNameFound = UA_FALSE;
+        for (size_t i =0; i<request->localeIdsSize && !appNameFound; i++) {
+            for (size_t j =0; j<registeredServer->serverNamesSize; j++) {
+                if (UA_String_equal(&request->localeIds[i], &registeredServer->serverNames[j].locale)) {
+                    retval |= UA_LocalizedText_copy(&registeredServer->serverNames[j], &target->applicationName);
+                    appNameFound = UA_TRUE;
+                    break;
+                }
+            }
+        }
+    } else if (registeredServer->serverNamesSize){
+        // just take the first name
+        retval |= UA_LocalizedText_copy(&registeredServer->serverNames[0], &target->applicationName);
+    }
+
+    target->applicationType = registeredServer->serverType;
+    retval |= UA_String_copy(&registeredServer->gatewayServerUri, &target->gatewayServerUri);
+    // TODO where do we get the discoveryProfileUri for application data?
+
+    target->discoveryUrlsSize = registeredServer->discoveryUrlsSize;
+    if (registeredServer->discoveryUrlsSize) {
+        target->discoveryUrls = UA_malloc(sizeof(UA_String) * registeredServer->discoveryUrlsSize);
+        if (!target->discoveryUrls) {
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+        for (size_t i = 0; i<registeredServer->discoveryUrlsSize; i++) {
+            retval |= UA_String_copy(&registeredServer->discoveryUrls[i], &target->discoveryUrls[i]);
+        }
+    }
+
+    return retval;
+}
+#endif
 
 void Service_FindServers(UA_Server *server, UA_Session *session,
                          const UA_FindServersRequest *request, UA_FindServersResponse *response) {
     UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing FindServersRequest");
-    /* copy ApplicationDescription from the config */
-    UA_ApplicationDescription *descr = UA_malloc(sizeof(UA_ApplicationDescription));
-    if(!descr) {
-        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-        return;
+
+
+    size_t foundServersSize = 0;
+    UA_ApplicationDescription *foundServers = NULL;
+
+    UA_Boolean addSelf = UA_FALSE;
+    // temporarily store all the pointers which we found to avoid reiterating through the list
+    UA_RegisteredServer **foundServerFilteredPointer = NULL;
+
+#ifdef UA_ENABLE_DISCOVERY
+    // check if client only requested a specific set of servers
+    if (request->serverUrisSize) {
+
+        foundServerFilteredPointer = UA_malloc(sizeof(UA_RegisteredServer*) * server->registeredServersSize);
+        if(!foundServerFilteredPointer) {
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            return;
+        }
+
+        for (size_t i=0; i<request->serverUrisSize; i++) {
+            if (!addSelf && UA_String_equal(&request->serverUris[i], &server->config.applicationDescription.applicationUri)) {
+                addSelf = UA_TRUE;
+            } else {
+                registeredServer_list_entry* current;
+                LIST_FOREACH(current, &server->registeredServers, pointers) {
+                    if (UA_String_equal(&current->registeredServer.serverUri, &request->serverUris[i])) {
+                        foundServerFilteredPointer[foundServersSize++] = &current->registeredServer;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (addSelf)
+            foundServersSize++;
+
+    } else {
+        addSelf = true;
+
+        // self + registered servers
+        foundServersSize = 1 + server->registeredServersSize;
     }
-    response->responseHeader.serviceResult =
-        UA_ApplicationDescription_copy(&server->config.applicationDescription, descr);
-    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-        UA_free(descr);
-        return;
+#else
+    if (request->serverUrisSize) {
+        for (size_t i=0; i<request->serverUrisSize; i++) {
+            if (UA_String_equal(&request->serverUris[i], &server->config.applicationDescription.applicationUri)) {
+                addSelf = UA_TRUE;
+                foundServersSize = 1;
+                break;
+            }
+        }
+    } else {
+        addSelf = UA_TRUE;
+        foundServersSize = 1;
     }
+#endif
 
-    /* add the discoveryUrls from the networklayers */
-    UA_String *disc = UA_realloc(descr->discoveryUrls, sizeof(UA_String) *
-                                 (descr->discoveryUrlsSize + server->config.networkLayersSize));
-    if(!disc) {
-        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-        UA_ApplicationDescription_delete(descr);
-        return;
-    }
-    size_t existing = descr->discoveryUrlsSize;
-    descr->discoveryUrls = disc;
-    descr->discoveryUrlsSize += server->config.networkLayersSize;
+    if (foundServersSize) {
+        foundServers = UA_malloc(sizeof(UA_ApplicationDescription) * foundServersSize);
+        if (!foundServers) {
+            if (foundServerFilteredPointer)
+                UA_free(foundServerFilteredPointer);
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            return;
+        }
+
+        if (addSelf) {
+            /* copy ApplicationDescription from the config */
+
+            response->responseHeader.serviceResult |= UA_ApplicationDescription_copy(&server->config.applicationDescription, &foundServers[0]);
+            if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+                UA_free(foundServers);
+                if (foundServerFilteredPointer)
+                    UA_free(foundServerFilteredPointer);
+                return;
+            }
+
+            /* add the discoveryUrls from the networklayers */
+            UA_String* disc = UA_realloc(foundServers[0].discoveryUrls, sizeof(UA_String) *
+                                                                                   (foundServers[0].discoveryUrlsSize +
+                                                                                    server->config.networkLayersSize));
+            if (!disc) {
+                response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+                UA_free(foundServers);
+                if (foundServerFilteredPointer)
+                    UA_free(foundServerFilteredPointer);
+                return;
+            }
+            size_t existing = foundServers[0].discoveryUrlsSize;
+            foundServers[0].discoveryUrls = disc;
+            foundServers[0].discoveryUrlsSize += server->config.networkLayersSize;
+
+            // TODO: Add nl only if discoveryUrl not already present
+            for (size_t i = 0; i < server->config.networkLayersSize; i++) {
+                UA_ServerNetworkLayer* nl = &server->config.networkLayers[i];
+                UA_String_copy(&nl->discoveryUrl, &foundServers[0].discoveryUrls[existing + i]);
+            }
+        }
+#ifdef UA_ENABLE_DISCOVERY
+
+        size_t currentIndex = 0;
+        if (addSelf)
+            currentIndex++;
+
+        // add all the registered servers to the list
 
-    // TODO: Add nl only if discoveryUrl not already present
-    for(size_t i = 0; i < server->config.networkLayersSize; i++) {
-        UA_ServerNetworkLayer *nl = &server->config.networkLayers[i];
-        UA_String_copy(&nl->discoveryUrl, &descr->discoveryUrls[existing + i]);
+        if (foundServerFilteredPointer) {
+            // use filtered list because client only requested specific uris
+            // -1 because foundServersSize also includes this self server
+            size_t iterCount = addSelf ? foundServersSize - 1 : foundServersSize;
+            for (size_t i = 0; i < iterCount; i++) {
+                response->responseHeader.serviceResult = copyRegisteredServerToApplicationDescription(request, &foundServers[currentIndex++],
+                                                                                                      foundServerFilteredPointer[i]);
+                if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+                    UA_free(foundServers);
+                    UA_free(foundServerFilteredPointer);
+                    return;
+                }
+            }
+            UA_free(foundServerFilteredPointer);
+            foundServerFilteredPointer = NULL;
+        } else {
+            registeredServer_list_entry* current;
+            LIST_FOREACH(current, &server->registeredServers, pointers) {
+                response->responseHeader.serviceResult = copyRegisteredServerToApplicationDescription(request, &foundServers[currentIndex++],
+                                                                                                      &current->registeredServer);
+                if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+                    UA_free(foundServers);
+                    return;
+                }
+            }
+        }
+#endif
     }
 
-    response->servers = descr;
-    response->serversSize = 1;
+    if (foundServerFilteredPointer)
+        UA_free(foundServerFilteredPointer);
+
+    response->servers = foundServers;
+    response->serversSize = foundServersSize;
 }
 
 void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEndpointsRequest *request,
@@ -118,3 +278,133 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEn
         return;
     }
 }
+
+#ifdef UA_ENABLE_DISCOVERY
+void Service_RegisterServer(UA_Server *server, UA_Session *session,
+                         const UA_RegisterServerRequest *request, UA_RegisterServerResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing RegisterServerRequest");
+
+    registeredServer_list_entry *registeredServer_entry = NULL;
+
+    {
+        // find the server from the request in the registered list
+        registeredServer_list_entry* current;
+        LIST_FOREACH(current, &server->registeredServers, pointers) {
+            if (UA_String_equal(&current->registeredServer.serverUri, &request->server.serverUri)) {
+                registeredServer_entry = current;
+                break;
+            }
+        }
+    }
+
+    if (!request->server.isOnline) {
+        // server is shutting down. Remove it from the registered servers list
+        if (!registeredServer_entry) {
+            // server not found, show warning
+            UA_LOG_WARNING_SESSION(server->config.logger, session, "Could not unregister server %.*s. Not registered.", (int)request->server.serverUri.length, request->server.serverUri.data);
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTFOUND;
+            return;
+        }
+
+        // server found, remove from list
+        LIST_REMOVE(registeredServer_entry, pointers);
+#ifndef UA_ENABLE_MULTITHREADING
+        UA_free(registeredServer_entry);
+        server->registeredServersSize--;
+#else
+        server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, -1);
+        UA_Server_delayedFree(server, registeredServer_entry);
+#endif
+        response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
+        return;
+    }
+
+
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    if (!registeredServer_entry) {
+        // server not yet registered, register it by adding it to the list
+
+
+        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Registering new server: %.*s", (int)request->server.serverUri.length, request->server.serverUri.data);
+
+        registeredServer_entry = UA_malloc(sizeof(registeredServer_list_entry));
+        if(!registeredServer_entry) {
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            return;
+        }
+
+        LIST_INSERT_HEAD(&server->registeredServers, registeredServer_entry, pointers);
+#ifndef UA_ENABLE_MULTITHREADING
+        server->registeredServersSize++;
+#else
+        server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, 1);
+#endif
+
+    } else {
+        UA_RegisteredServer_deleteMembers(&registeredServer_entry->registeredServer);
+    }
+
+    // copy the data from the request into the list
+    UA_RegisteredServer_copy(&request->server, &registeredServer_entry->registeredServer);
+    registeredServer_entry->lastSeen = UA_DateTime_now();
+
+    response->responseHeader.serviceResult = retval;
+}
+
+/**
+ * Cleanup server registration:
+ * If the semaphore file path is set, then it just checks the existence of the file.
+ * When it is deleted, the registration is removed.
+ * If there is no semaphore file, then the registration will be removed if it is older than 60 minutes.
+ */
+void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime now) {
+
+    UA_DateTime timedOut = now;
+    // registration is timed out if lastSeen is older than 60 minutes.
+    timedOut -= 60*60*UA_SEC_TO_DATETIME;
+
+    registeredServer_list_entry* current, *temp;
+    LIST_FOREACH_SAFE(current, &server->registeredServers, pointers, temp) {
+
+        UA_Boolean semaphoreDeleted = UA_FALSE;
+
+        if (current->registeredServer.semaphoreFilePath.length) {
+            char* filePath = malloc(sizeof(char)*current->registeredServer.semaphoreFilePath.length+1);
+            memcpy( filePath, current->registeredServer.semaphoreFilePath.data, current->registeredServer.semaphoreFilePath.length );
+            filePath[current->registeredServer.semaphoreFilePath.length] = '\0';
+
+#ifdef _MSC_VER
+            semaphoreDeleted = _access( filePath, 0 ) == -1;
+#else
+            semaphoreDeleted = access( filePath, 0 ) == -1;
+#endif
+            free(filePath);
+        }
+
+
+        if (semaphoreDeleted || current->lastSeen < timedOut) {
+            if (semaphoreDeleted) {
+                UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                            "Registration of server with URI %.*s is removed because the semaphore file '%.*s' was deleted.",
+                            (int)current->registeredServer.serverUri.length, current->registeredServer.serverUri.data,
+                            (int)current->registeredServer.semaphoreFilePath.length, current->registeredServer.semaphoreFilePath.data);
+            } else {
+                UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                             "Registration of server with URI %.*s has timed out and is removed",
+                            (int)current->registeredServer.serverUri.length, current->registeredServer.serverUri.data);
+            }
+            LIST_REMOVE(current, pointers);
+            UA_RegisteredServer_deleteMembers(&current->registeredServer);
+#ifndef UA_ENABLE_MULTITHREADING
+            UA_free(current);
+            server->registeredServersSize--;
+#else
+            server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, -1);
+            UA_Server_delayedFree(server, current);
+#endif
+
+        }
+    }
+}
+
+#endif

+ 3 - 0
tools/schema/datatypes_minimal.txt

@@ -158,3 +158,6 @@ MonitoredItemModifyResult
 ModifyMonitoredItemsResponse
 SetMonitoringModeRequest
 SetMonitoringModeResponse
+RegisteredServer
+RegisterServerRequest
+RegisterServerResponse

+ 13 - 6
tools/travis/travis_linux_script.sh

@@ -51,7 +51,7 @@ else
         echo "Cross compile release build for MinGW 32 bit"
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw32.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON -DUA_BUILD_EXAMPLES=ON ..
-        make -j8
+        make -j
         zip -r open62541-win32.zip ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md server_static.exe server.exe client.exe client_static.exe libopen62541.dll libopen62541.dll.a open62541.h open62541.c
         cp open62541-win32.zip ..
         cd .. && rm build -rf
@@ -59,7 +59,7 @@ else
         echo "Cross compile release build for MinGW 64 bit"
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw64.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON -DUA_BUILD_EXAMPLES=ON ..
-        make -j8
+        make -j
         zip -r open62541-win64.zip ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md server_static.exe server.exe client.exe client_static.exe libopen62541.dll libopen62541.dll.a open62541.h open62541.c
         cp open62541-win64.zip ..
         cd .. && rm build -rf
@@ -67,7 +67,7 @@ else
         echo "Cross compile release build for 32-bit linux"
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-gcc-m32.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON ..
-        make -j8
+        make -j
         tar -pczf open62541-linux32.tar.gz ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md server_static server client_static client libopen62541.so open62541.h open62541.c
         cp open62541-linux32.tar.gz ..
         cd .. && rm build -rf
@@ -76,7 +76,7 @@ else
     echo "Compile release build for 64-bit linux"
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_ENABLE_AMALGAMATION=ON -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON ..
-    make -j8
+    make -j
     tar -pczf open62541-linux64.tar.gz ../../doc ../../server_cert.der ../LICENSE ../AUTHORS ../README.md server_static server client_static client libopen62541.so open62541.h open62541.c
     cp open62541-linux64.tar.gz ..
     cp open62541.h .. # copy single file-release
@@ -98,16 +98,23 @@ else
     echo "Compile multithreaded version"
     mkdir -p build && cd build
     cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_BUILD_EXAMPLESERVER=ON ..
-    make -j8
+    make -j
+    cd .. && rm build -rf
+
+    echo "Compile without discovery version"
+    mkdir -p build && cd build
+    cmake -DUA_ENABLE_DISCOVERY=OFF -DUA_BUILD_EXAMPLES=ON ..
+    make -j
     cd .. && rm build -rf
 
     #this run inclides full examples and methodcalls
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_METHODCALLS=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_BUILD_EXAMPLESERVER=ON -DUA_ENABLE_COVERAGE=ON ..
-    make -j8 && make test ARGS="-V"
+    make -j && make test ARGS="-V"
     echo "Run valgrind to see if the server leaks memory (just starting up and closing..)"
     (valgrind --leak-check=yes --error-exitcode=3 ./server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
+    
     # only run coveralls on main repo, otherwise it fails uploading the files
     echo "-> Current repo: ${TRAVIS_REPO_SLUG}"
     if ([ "$CC" = "gcc-4.8" ] || [ "$CC" = "gcc" ]) && [ "${TRAVIS_REPO_SLUG}" = "open62541/open62541" ]; then

+ 9 - 3
tools/travis/travis_osx_script.sh

@@ -17,7 +17,7 @@ else
     echo "Compile release build for OS X"
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_ENABLE_AMALGAMATION=ON -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON -DUA_BUILD_DOCUMENTATION=ON -DUA_GENERATE_SELFSIGNED=ON ..
-    make -j8
+    make -j
     tar -pczf open62541-osx.tar.gz ../doc ../server_cert.der ../LICENSE ../AUTHORS ../README.md server_static server client_static client libopen62541.dylib open62541.h open62541.c
     cp open62541-osx.tar.gz ..
     cp open62541.h .. #copy single file-release
@@ -27,13 +27,19 @@ else
     echo "Compile multithreaded version"
     mkdir -p build && cd build
     cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_BUILD_EXAMPLESERVER=ON ..
-    make -j8
+    make -j
+    cd .. && rm -rf build
+
+    echo "Compile without discovery version"
+    mkdir -p build && cd build
+    cmake -DUA_ENABLE_DISCOVERY=OFF -DUA_BUILD_EXAMPLESERVER=ON ..
+    make -j
     cd .. && rm -rf build
 
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_DEMO_NODESET=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_BUILD_EXAMPLESERVER=ON -DUA_ENABLE_COVERAGE=ON ..
-    make -j8 && make test
+    make -j && make test
     echo "Run valgrind to see if the server leaks memory (just starting up and closing..)"
     (valgrind --error-exitcode=3 ./server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
     cd .. && rm -rf build