Kaynağa Gözat

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 yıl önce
ebeveyn
işleme
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_NODEMANAGEMENT "Enable dynamic addition and removal of nodes" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
 option(UA_ENABLE_MULTITHREADING "Enable multithreading" OFF)
 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_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 if(UA_ENABLE_COVERAGE)
 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>)
     add_executable(server_repeated_job ${PROJECT_SOURCE_DIR}/examples/server_repeated_job.c $<TARGET_OBJECTS:open62541-object>)
     target_link_libraries(server_repeated_job ${LIBS})
     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
     add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
                       PRE_BUILD
                       PRE_BUILD
                       COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/pyUANamespace/generate_open62541CCode.py
                       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)
 add_executable(client_firstSteps client_firstSteps.c)
 target_link_libraries(client_firstSteps ${LIBS})
 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)
 if(NOT UA_ENABLE_AMALGAMATION)
 	add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
 	add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/nodeset.h ${PROJECT_BINARY_DIR}/src_generated/nodeset.c
 		               PRE_BUILD
 		               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_MULTITHREADING
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
+#cmakedefine UA_ENABLE_DISCOVERY
 #cmakedefine UA_ENABLE_TYPENAMES
 #cmakedefine UA_ENABLE_TYPENAMES
 #cmakedefine UA_ENABLE_GENERATE_NAMESPACE0
 #cmakedefine UA_ENABLE_GENERATE_NAMESPACE0
 #cmakedefine UA_ENABLE_EXTERNAL_NAMESPACES
 #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_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
                                UA_NodeIteratorCallback callback, void *handle);
                                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
  * Method Call
  * ----------- */
  * ----------- */

+ 121 - 0
src/server/ua_server.c

@@ -6,6 +6,11 @@
 #include "ua_services.h"
 #include "ua_services.h"
 #include "ua_nodeids.h"
 #include "ua_nodeids.h"
 
 
+#ifdef UA_ENABLE_DISCOVERY
+#include "ua_client.h"
+#include "ua_config_standard.h"
+#endif
+
 #ifdef UA_ENABLE_GENERATE_NAMESPACE0
 #ifdef UA_ENABLE_GENERATE_NAMESPACE0
 #include "ua_namespaceinit_generated.h"
 #include "ua_namespaceinit_generated.h"
 #endif
 #endif
@@ -264,6 +269,15 @@ void UA_Server_delete(UA_Server *server) {
     UA_Array_delete(server->endpointDescriptions, server->endpointDescriptionsSize,
     UA_Array_delete(server->endpointDescriptions, server->endpointDescriptionsSize,
                     &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
                     &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
 #ifdef UA_ENABLE_MULTITHREADING
     pthread_cond_destroy(&server->dispatchQueue_condition);
     pthread_cond_destroy(&server->dispatchQueue_condition);
 #endif
 #endif
@@ -275,6 +289,9 @@ static void UA_Server_cleanup(UA_Server *server, void *_) {
     UA_DateTime now = UA_DateTime_now();
     UA_DateTime now = UA_DateTime_now();
     UA_SessionManager_cleanupTimedOut(&server->sessionManager, now);
     UA_SessionManager_cleanupTimedOut(&server->sessionManager, now);
     UA_SecureChannelManager_cleanupTimedOut(&server->secureChannelManager, now);
     UA_SecureChannelManager_cleanupTimedOut(&server->secureChannelManager, now);
+#ifdef UA_ENABLE_DISCOVERY
+    UA_Discovery_cleanupTimedOut(server, now);
+#endif
 }
 }
 
 
 static UA_StatusCode
 static UA_StatusCode
@@ -554,6 +571,12 @@ UA_Server * UA_Server_new(const UA_ServerConfig config) {
                       .job.methodCall = {.method = UA_Server_cleanup, .data = NULL} };
                       .job.methodCall = {.method = UA_Server_cleanup, .data = NULL} };
     UA_Server_addRepeatedJob(server, cleanup, 10000, 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();
     server->startTime = UA_DateTime_now();
 
 
     /**************/
     /**************/
@@ -1490,3 +1513,101 @@ UA_CallMethodResult UA_Server_call(UA_Server *server, const UA_CallMethodRequest
     return result;
     return result;
 }
 }
 #endif
 #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];
         *responseType = &UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE];
         *requiresSession = false;
         *requiresSession = false;
         break;
         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:
     case UA_NS0ID_CREATESESSIONREQUEST:
         *service = (UA_Service)Service_CreateSession;
         *service = (UA_Service)Service_CreateSession;
         *requestType = &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST];
         *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;
 extern UA_THREAD_LOCAL UA_Session* methodCallSession;
 #endif
 #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 {
 struct UA_Server {
     /* Meta */
     /* Meta */
     UA_DateTime startTime;
     UA_DateTime startTime;
@@ -51,6 +59,12 @@ struct UA_Server {
     /* Address Space */
     /* Address Space */
     UA_NodeStore *nodestore;
     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;
     size_t namespacesSize;
     UA_String *namespaces;
     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,
                           const UA_GetEndpointsRequest *request,
                           UA_GetEndpointsResponse *response);
                           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
  * SecureChannel Service Set

+ 318 - 28
src/server/ua_services_discovery.c

@@ -1,43 +1,203 @@
 #include "ua_server_internal.h"
 #include "ua_server_internal.h"
 #include "ua_services.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,
 void Service_FindServers(UA_Server *server, UA_Session *session,
                          const UA_FindServersRequest *request, UA_FindServersResponse *response) {
                          const UA_FindServersRequest *request, UA_FindServersResponse *response) {
     UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing FindServersRequest");
     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,
 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;
         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
 ModifyMonitoredItemsResponse
 SetMonitoringModeRequest
 SetMonitoringModeRequest
 SetMonitoringModeResponse
 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"
         echo "Cross compile release build for MinGW 32 bit"
         mkdir -p build && cd build
         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 ..
         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
         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 ..
         cp open62541-win32.zip ..
         cd .. && rm build -rf
         cd .. && rm build -rf
@@ -59,7 +59,7 @@ else
         echo "Cross compile release build for MinGW 64 bit"
         echo "Cross compile release build for MinGW 64 bit"
         mkdir -p build && cd build
         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 ..
         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
         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 ..
         cp open62541-win64.zip ..
         cd .. && rm build -rf
         cd .. && rm build -rf
@@ -67,7 +67,7 @@ else
         echo "Cross compile release build for 32-bit linux"
         echo "Cross compile release build for 32-bit linux"
         mkdir -p build && cd build
         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 ..
         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
         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 ..
         cp open62541-linux32.tar.gz ..
         cd .. && rm build -rf
         cd .. && rm build -rf
@@ -76,7 +76,7 @@ else
     echo "Compile release build for 64-bit linux"
     echo "Compile release build for 64-bit linux"
     mkdir -p build && cd build
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_ENABLE_AMALGAMATION=ON -DUA_BUILD_EXAMPLESERVER=ON -DUA_BUILD_EXAMPLECLIENT=ON ..
     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
     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-linux64.tar.gz ..
     cp open62541.h .. # copy single file-release
     cp open62541.h .. # copy single file-release
@@ -98,16 +98,23 @@ else
     echo "Compile multithreaded version"
     echo "Compile multithreaded version"
     mkdir -p build && cd build
     mkdir -p build && cd build
     cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_BUILD_EXAMPLESERVER=ON ..
     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
     cd .. && rm build -rf
 
 
     #this run inclides full examples and methodcalls
     #this run inclides full examples and methodcalls
     echo "Debug build and unit tests (64 bit)"
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     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 ..
     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..)"
     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);
     (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
     # only run coveralls on main repo, otherwise it fails uploading the files
     echo "-> Current repo: ${TRAVIS_REPO_SLUG}"
     echo "-> Current repo: ${TRAVIS_REPO_SLUG}"
     if ([ "$CC" = "gcc-4.8" ] || [ "$CC" = "gcc" ]) && [ "${TRAVIS_REPO_SLUG}" = "open62541/open62541" ]; then
     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"
     echo "Compile release build for OS X"
     mkdir -p build && cd build
     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 ..
     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
     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-osx.tar.gz ..
     cp open62541.h .. #copy single file-release
     cp open62541.h .. #copy single file-release
@@ -27,13 +27,19 @@ else
     echo "Compile multithreaded version"
     echo "Compile multithreaded version"
     mkdir -p build && cd build
     mkdir -p build && cd build
     cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_BUILD_EXAMPLESERVER=ON ..
     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
     cd .. && rm -rf build
 
 
     echo "Debug build and unit tests (64 bit)"
     echo "Debug build and unit tests (64 bit)"
     mkdir -p build && cd build
     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 ..
     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..)"
     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);
     (valgrind --error-exitcode=3 ./server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
     cd .. && rm -rf build
     cd .. && rm -rf build