Browse Source

Code review: @Pro's MDNS implementation (#732)

* 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

* LDS customizable timeout for automatic de-register (#695)

* Enable valgrind for unit tests and fix memory leaks

* Fix build errors introduced by cmake rewrite in current master

* LDS customizable timeout for automatic de-register

* LDS-ME (using mDNS) (#730)

* Enable coveralls for Pro/open62541

* Move periodic server register functionality into core implementation

* Add mDNS library as dependency

* Cleanup and enable Mingw x64 on Appveyor

* Set define for mDNS dll

* Fix memory leak

* Check for defined variable to aviod deleting root dir

* Add LDS-ME

LDS Multicast Extension using mDNS for service discovery according to OPC UA Part 12
To be implemented:
- RegisterServer2
- FindServersOnNetwork
- GDS has to call FindServers on all known LDS

* Update to most recent schema definition from https://opcfoundation.org/UA/schemas/Opc.Ua.Types.bsd.xml

* Update to most recent node ids from https://opcfoundation.org/UA/schemas/NodeIds.csv

* Implement RegisterServer2 and FindServersOnNetwork

* Fix memory leak

* Use clang-3.7 because older scan build finds invalid bug

* Fix for mDNS class comparison if value is signed

* cosmetic cleanup (untabify, indention)

* fix after rebase

* Use monotonic clock for timeouts

* Fix crash of register server caused by invalid memory

* Set correct job id for periodic server register

* Use valid SD DNS name ([servername]._opcua-tcp._tcp.[hostname]. -> [servername]-[hostname]._opcua-tcp._tcp.local.)
Set A record for SD DNS

* Remove output of last seen time, since it is now monotonic and thus not human readable

* Fix removal of all remaining DNS records

* Fix discovery test due to previous changes

* Get interface ip addresses for windows

* Update mdnsd submodule

* Additional check output

* Also set A record for hostname

* Send multicast query to _opcua-tcp._tcp.local to find other servers after startup

* Update mdnsd

* Update mdnsd

* Limit mDNS label to max 63 chars according to DNS RFC

* Fix rebase issues

* fix a cppcheck issue for unreachable code

* Untabify

* Check if path or caps is set in TXT record

* Do not remove (TTL=0) `_services._dns-sd._udp.local.` because it may still be used by other PC in the net.

* Update mdnsd

* Fix correct construction of hostname if it is shorter than 63 chars

* Fix correct construction of hostname if it is shorter than 63 chars

* fixup

* Fix correct construction of hostname if it is shorter than 63 chars

* fixup

* Fix correct construction of hostname if it is shorter than 63 chars

* Fix correct construction of hostname if it is shorter than 63 chars

* fixup

* RegisterServer does not require a session

* untabify where it lead to compiler-warnings (if-guards)

* Update mdnsd

* Allow changing the LDS URL for RegisterServer and set default value if NULL

* Typo

* add Callbacks for RegisterServer and mDNS detection

* untabify

* Setting correct port for DiscoveryServer using NetworkLayer information. Better use of UA_EndpointUrl_split

* Include isTxtReceived parameter in callback

* Fix access after free

* Add EndpointUrl to CreateSessionRequest

* Copy&Paste error

* Fix #769: Wrong NodeIDs offset for BinaryEncoding

* fixup

* Fix #769: Wrong NodeIDs offset for BinaryEncoding

* Add additional node ids for previous change of binary encoding offset

* Also check for UA_STATUSCODE_BADSERVICEUNSUPPORTED

* Check correct response code

* Avoid double loops for finding correct node id

* Remove findDataType

* Disable xmlEncodingId for now

* Use UA_UInt64 for repeated job interval

UA_UInt32 for interval in nsec means maximum interval of 429 seconds -> too small

* Fix conversion to UInt64

* Move SplitEndoint into ua_connection.h

* Create specific method for splitting endpoint url. Fix #778

* Windows wants strncpy_s

* Port is max 6 chars

* Fix uninitialized parameter warning

* Handle path without leading slash

* Additional unit tests to cover discovery functionalities

* Untabify

* Fix broken build after merge

* Fix after merge

* fix after merge

* fix after merge

* Removed undef SLIST

* Readd missing BSD_SOURCE define needed for mdns

* Use correct define for amalgamation

* Add missing header for cygwin on Windows

* Fix compilation errors on windows using Visual Studio

* Fix double free

* If applicationType is set to DiscoveryServer, set it to Server

See http://forum.unified-automation.com/topic1987.html

* Fixup for merge

* fix output of UA_String

* Small bug fixes

* Fixup after merge

* Fixup 2 after merge

* Add missing library if win32 compile

* Delete server after test

* Fix wrong positive clang warnings

* Add additional assert for NULL

* Add additional assert for NULL

* Fix MS build

* Additional NULL check for clang analyzer

* Fix MS Build

* Fix #950 wrong requestServer structure passed to registerServer callback.

Thanks @XB32Z

* Reintegrate concepts of PR #932 which were removed in previous merge

Don't copy elements for RegisterServer call

* Fix build on appveyor

* Update mdns

* make mdns non-default; cosmetic improvements

* Enable discovery for unit tests

* Fix missing semicolon

* Travis fold log

* Travis fold log

* Avoid variable exit of local scope

* Refactor: move mdns functions into separate header

* Refactor: simplify and extract functions

* Add UA_ENABLE_DISCOVERY_SEMAPHORE to disable semaphore support on emedded devices

* Fix bug introduced in commit 83edcef3c1f1b1e816151d56e1aa0f40e674fa11

* Enable mdns for unit tests

* Cleaner travis outpu and build separation

* Untabify

Command used:
`find . \( -name '*.c' -o -name '*.h' \) -not \( -path "./deps/*" -prune \) ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;`

* Untabify master

Command used:
`find . \( -name '*.c' -o -name '*.h' \) -not \( -path "./deps/*" -prune \) ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;`

* Untabify

Command used:
`find . \( -name '*.c' -o -name '*.h' \) -not \( -path "./deps/*" -prune \) ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;`

* Client must not create a session for FindServer calls.

Non-Multicast functionalities tested with OPC Foundation LDS Server. Fix #938

* Fix memleak

* Use correct uri

* untabify

* merge changes by @Pro

* split out mdns service functionality into separate file

* make local functions static

* split a large function, mark malloc that needs to be return-checked

* split large functions

* Return valid status code

* Add service description

* untabify

* Update mdns library

* Split loop into separate function

* Split overlong line

* Use correct define

* Fix build on VS2008

* fixup merge

* fixup merge

* Fix build on VS 2008

* Hide no_session connect for cleaner public api
Stefan Profanter 7 years ago
parent
commit
14df336430

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "deps/mdnsd"]
+	path = deps/mdnsd
+	url = https://github.com/Pro/mdnsd.git

+ 36 - 0
CMakeLists.txt

@@ -6,6 +6,8 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake")
 find_package(PythonInterp REQUIRED)
 find_package(Git)
 
+set(MDNSD_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported" FORCE)
+
 #############################
 # Compiled binaries folders #
 #############################
@@ -57,6 +59,10 @@ option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes at runtime" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
 option(UA_ENABLE_DISCOVERY "Enable Discovery Service (LDS)" ON)
+option(UA_ENABLE_DISCOVERY_MULTICAST "Enable Discovery Service with multicast support (LDS-ME)" OFF)
+# Semaphores/file system may not be available on embedded devices. It can be disabled with the following option
+option(UA_ENABLE_DISCOVERY_SEMAPHORE "Enable Discovery Semaphore support" ON)
+mark_as_advanced(UA_ENABLE_DISCOVERY_SEMAPHORE)
 option(UA_ENABLE_AMALGAMATION "Concatenate the library to a single file open62541.h/.c" OFF)
 option(UA_ENABLE_COVERAGE "Enable gcov coverage" OFF)
 option(BUILD_SHARED_LIBS "Enable building of shared libraries (dll/so)" OFF)
@@ -68,6 +74,11 @@ if(UA_ENABLE_COVERAGE)
   set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
 endif()
 
+if(UA_ENABLE_DISCOVERY_MULTICAST AND NOT UA_ENABLE_DISCOVERY)
+    MESSAGE(WARNING "UA_ENABLE_DISCOVERY_MULTICAST is enabled, but not UA_ENABLE_DISCOVERY. UA_ENABLE_DISCOVERY_MULTICAST will be set to OFF")
+    SET(UA_ENABLE_DISCOVERY_MULTICAST OFF CACHE BOOL "Enable Discovery Service with multicast support (LDS-ME)" FORCE)
+endif()
+
 # Advanced options
 option(UA_ENABLE_MULTITHREADING "Enable multithreading (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_MULTITHREADING)
@@ -115,6 +126,9 @@ mark_as_advanced(UA_BUILD_SELFSIGNED_CERTIFICATE)
 set(UA_DYNAMIC_LINKING OFF)
 if(BUILD_SHARED_LIBS)
   set(UA_DYNAMIC_LINKING ON)
+  if (UA_ENABLE_DISCOVERY_MULTICAST)
+      set(MDNSD_DYNAMIC_LINKING ON)
+  endif()
 endif()
 
 # Force compilation with as C++
@@ -185,6 +199,10 @@ endif()
 
 file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src_generated")
 configure_file("include/ua_config.h.in" "${PROJECT_BINARY_DIR}/src_generated/ua_config.h")
+
+if (UA_ENABLE_DISCOVERY_MULTICAST)
+    configure_file("deps/mdnsd/libmdnsd/mdnsd_config.h.in" "${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h")
+endif()
 include_directories(${PROJECT_BINARY_DIR}/src_generated
                     ${PROJECT_SOURCE_DIR}/include
                     ${PROJECT_SOURCE_DIR}/deps)
@@ -248,6 +266,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_nodestore_concurrent.c
                 # services
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_discovery.c
+                ${PROJECT_SOURCE_DIR}/src/server/ua_services_discovery_multicast.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_securechannel.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_session.c
                 ${PROJECT_SOURCE_DIR}/src/server/ua_services_attribute.c
@@ -291,6 +310,13 @@ if(UA_ENABLE_NONSTANDARD_UDP)
     list(APPEND exported_headers ${PROJECT_SOURCE_DIR}/plugins/ua_network_udp.h)
 endif()
 
+if (UA_ENABLE_DISCOVERY_MULTICAST)
+    # prepend in list, otherwise it complains that winsock2.h has to be included before windows.h
+    set(internal_headers "${PROJECT_BINARY_DIR}/src_generated/mdnsd_config.h" ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.h  ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.h ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.h ${internal_headers} )
+    list(APPEND internal_headers ${PROJECT_SOURCE_DIR}/src/server/ua_mdns_internal.h)
+    set(lib_sources ${PROJECT_SOURCE_DIR}/src/server/ua_mdns.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/1035.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/xht.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/sdtxt.c ${PROJECT_SOURCE_DIR}/deps/mdnsd/libmdnsd/mdnsd.c ${lib_sources})
+endif()
+
 #########################
 # Generate source files #
 #########################
@@ -423,11 +449,20 @@ target_link_libraries(open62541 ${open62541_LIBRARIES})
 target_compile_definitions(open62541-object PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
 target_compile_definitions(open62541 PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)
 
+if (UA_ENABLE_DISCOVERY_MULTICAST)
+    target_compile_definitions(open62541-object PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
+    target_compile_definitions(open62541 PRIVATE -DMDNSD_DYNAMIC_LINKING_EXPORT)
+endif()
+
 # Generate properly versioned shared library links on Linux
 SET_TARGET_PROPERTIES(open62541 PROPERTIES SOVERSION 0 VERSION "${OPEN62541_VER_MAJOR}.${OPEN62541_VER_MINOR}.${OPEN62541_VER_PATCH}")
 
 if(WIN32)
     target_link_libraries(open62541 ws2_32)
+    if (UA_ENABLE_DISCOVERY_MULTICAST)
+        target_link_libraries(open62541 iphlpapi) # for GetAdaptersAddresses
+        list(APPEND open62541_LIBRARIES iphlpapi)
+    endif()
 endif()
 
 ##########################
@@ -470,6 +505,7 @@ add_custom_target(lint ${CLANG_TIDY_PROGRAM}
                   -I${PROJECT_SOURCE_DIR}/src/server
                   -I${PROJECT_SOURCE_DIR}/src/client
                   -I${PROJECT_BINARY_DIR}/src_generated
+                  -DUA_NO_AMALGAMATION
                   DEPENDS ${lib_sources}
                   COMMENT "Run clang-tidy on the library")
 add_dependencies(lint open62541)

+ 1 - 0
deps/mdnsd

@@ -0,0 +1 @@
+Subproject commit 47d76ed02243527d20930779c0e2242b77f011ca

+ 6 - 3
examples/CMakeLists.txt

@@ -100,9 +100,12 @@ if(UA_BUILD_SELFSIGNED_CERTIFICATE)
 endif()
 
 if(UA_ENABLE_DISCOVERY)
-    add_example(discovery_server_discovery discovery/server_discovery.c)
+  add_example(discovery_server_lds discovery/server_lds.c)
 
-    add_example(discovery_server_register discovery/server_register.c)
+  add_example(discovery_server_register discovery/server_register.c)
 
-    add_example(discovery_client_find_servers discovery/client_find_servers.c)
+  add_example(discovery_client_find_servers discovery/client_find_servers.c)
+  if(UA_ENABLE_DISCOVERY_MULTICAST)
+    add_example(discovery_server_multicast discovery/server_multicast.c)
+  endif()
 endif()

+ 87 - 120
examples/discovery/client_find_servers.c

@@ -1,128 +1,88 @@
 /* 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)
+ * This client requests all the available servers from the discovery server (see server_lds.c)
  * and then calls GetEndpoints on the returned list of servers.
  */
 
 #include <stdio.h>
 #include <stdlib.h>
-#include "open62541.h"
+#include <open62541.h>
 
 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;
-    }
+#define DISCOVERY_SERVER_ENDPOINT "opc.tcp://localhost:4840"
 
-    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_Array_delete(request.serverUris, request.serverUrisSize, &UA_TYPES[UA_TYPES_STRING]);
-    //UA_Array_delete(request.localeIds, request.localeIdsSize, &UA_TYPES[UA_TYPES_STRING]);
-
-    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;
-    }
+int main(void) {
 
-    *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);
+    /*
+     * Example for calling FindServersOnNetwork
+     */
 
-    UA_Client_disconnect(client);
-    UA_Client_delete(client);
-    return (int) UA_STATUSCODE_GOOD;
-}
+    {
+        UA_ServerOnNetwork *serverOnNetwork = NULL;
+        size_t serverOnNetworkSize = 0;
 
-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;
-    }
+        UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+        UA_StatusCode retval = UA_Client_findServersOnNetwork(client, DISCOVERY_SERVER_ENDPOINT, 0, 0,
+                                                              0, NULL, &serverOnNetworkSize, &serverOnNetwork);
+        if (retval != UA_STATUSCODE_GOOD) {
+            UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not call FindServersOnNetwork service. Is the discovery server started? StatusCode %s",
+                         UA_StatusCode_name(retval));
+            UA_Client_delete(client);
+            return (int) retval;
+        }
+
+        // output all the returned/registered servers
+        for (size_t i = 0; i < serverOnNetworkSize; i++) {
+            UA_ServerOnNetwork *server = &serverOnNetwork[i];
+            printf("Server[%lu]: %.*s", (unsigned long) i, (int) server->serverName.length, server->serverName.data);
+            printf("\n\tRecordID: %d", server->recordId);
+            printf("\n\tDiscovery URL: %.*s", (int) server->discoveryUrl.length, server->discoveryUrl.data);
+            printf("\n\tCapabilities: ");
+            for (size_t j = 0; j < server->serverCapabilitiesSize; j++) {
+                printf("%.*s,", (int) server->serverCapabilities[j].length, server->serverCapabilities[j].data);
+            }
+            printf("\n\n");
+        }
 
-    *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_Array_delete(serverOnNetwork, serverOnNetworkSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]);
     }
-    UA_GetEndpointsResponse_deleteMembers(&response);
-    return UA_STATUSCODE_GOOD;
-}
 
-int main(void) {
+    /*
+     * Example for calling FindServers
+     */
 
-    UA_ApplicationDescription* applicationDescriptionArray = NULL;
+
+    UA_ApplicationDescription *applicationDescriptionArray = NULL;
     size_t applicationDescriptionArraySize = 0;
 
-    UA_StatusCode retval = FindServers("opc.tcp://localhost:4840",
-                                       &applicationDescriptionArraySize,
-                                       &applicationDescriptionArray);
-    if(retval != UA_STATUSCODE_GOOD) {
+    UA_StatusCode retval;
+    {
+        UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+        retval = UA_Client_findServers(client, DISCOVERY_SERVER_ENDPOINT, 0, NULL, 0, NULL,
+                                       &applicationDescriptionArraySize, &applicationDescriptionArray);
+        UA_Client_delete(client);
+    }
+    if (retval != UA_STATUSCODE_GOOD) {
         UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not call FindServers service. "
-                     "Is the discovery server started? StatusCode %s", UA_StatusCode_name(retval));
-        return (int)retval;
+                "Is the discovery server started? StatusCode %s", UA_StatusCode_name(retval));
+        return (int) retval;
     }
 
     // output all the returned/registered servers
-    for (size_t i=0; i<applicationDescriptionArraySize; i++) {
+    for (size_t i = 0; i < applicationDescriptionArraySize; i++) {
         UA_ApplicationDescription *description = &applicationDescriptionArray[i];
-        printf("Server[%lu]: %.*s", (unsigned long)i, (int)description->applicationUri.length,
+        printf("Server[%lu]: %.*s", (unsigned long) i, (int) description->applicationUri.length,
                description->applicationUri.data);
-        printf("\n\tName: %.*s", (int)description->applicationName.text.length,
+        printf("\n\tName: %.*s", (int) description->applicationName.text.length,
                description->applicationName.text.data);
-        printf("\n\tProduct URI: %.*s", (int)description->productUri.length,
+        printf("\n\tApplication URI: %.*s", (int) description->applicationUri.length,
+               description->applicationUri.data);
+        printf("\n\tProduct URI: %.*s", (int) description->productUri.length,
                description->productUri.data);
         printf("\n\tType: ");
-        switch(description->applicationType) {
+        switch (description->applicationType) {
             case UA_APPLICATIONTYPE_SERVER:
                 printf("Server");
                 break;
@@ -139,9 +99,9 @@ int main(void) {
                 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,
+        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");
@@ -154,7 +114,7 @@ int main(void) {
 
     printf("-------- Server Endpoints --------\n");
 
-    for (size_t i=0; i<applicationDescriptionArraySize; i++) {
+    for (size_t i = 0; i < applicationDescriptionArraySize; i++) {
         UA_ApplicationDescription *description = &applicationDescriptionArray[i];
         if (description->discoveryUrlsSize == 0) {
             UA_LOG_INFO(logger, UA_LOGCATEGORY_CLIENT,
@@ -163,41 +123,48 @@ int main(void) {
             continue;
         }
 
-        printf("\nEndpoints for Server[%lu]: %.*s", (unsigned long)i,
-               (int)description->applicationUri.length, description->applicationUri.data);
+        printf("\nEndpoints for Server[%lu]: %.*s\n", (unsigned long) i,
+               (int) description->applicationUri.length, description->applicationUri.data);
 
         UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
 
-        char* discoveryUrl = (char*)malloc(sizeof(char)*description->discoveryUrls[0].length+1);
-        memcpy( discoveryUrl, description->discoveryUrls[0].data, description->discoveryUrls[0].length );
+        char *discoveryUrl = (char *) 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;
+        UA_EndpointDescription *endpointArray = NULL;
         size_t endpointArraySize = 0;
-        retval = GetEndpoints(client, &description->discoveryUrls[0], &endpointArraySize, &endpointArray);
-        if(retval != UA_STATUSCODE_GOOD) {
+        retval = UA_Client_getEndpoints(client, discoveryUrl, &endpointArraySize, &endpointArray);
+        free(discoveryUrl);
+        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,
+        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);
+            printf("\n\t\tSecurity Mode: ");
+            switch (endpoint->securityMode) {
+                case UA_MESSAGESECURITYMODE_INVALID:
+                    printf("Invalid");
+                case UA_MESSAGESECURITYMODE_NONE:
+                    printf("None");
+                case UA_MESSAGESECURITYMODE_SIGN:
+                    printf("Sign");
+                case UA_MESSAGESECURITYMODE_SIGNANDENCRYPT:
+                    printf("Sign and Encrypt");
+            }
+            printf("\n\t\tSecurity profile URI: %.*s", (int) endpoint->securityPolicyUri.length,
+                   endpoint->securityPolicyUri.data);
+            printf("\n\t\tSecurity Level: %d", endpoint->securityLevel);
         }
 
         UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
-        UA_Client_disconnect(client);
         UA_Client_delete(client);
     }
 
@@ -206,5 +173,5 @@ int main(void) {
     UA_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize,
                     &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
 
-    return (int)UA_STATUSCODE_GOOD;
+    return (int) UA_STATUSCODE_GOOD;
 }

+ 11 - 3
examples/discovery/server_discovery.c

@@ -16,13 +16,19 @@ static void stopHandler(int sig) {
 }
 
 int main(void) {
-    signal(SIGINT,  stopHandler);
+    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_String_fromChars("urn:open62541.example.local_discovery_server");
+    config.mdnsServerName = UA_String_fromChars("LDS");
+    // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
+    config.serverCapabilitiesSize = 1;
+    UA_String *caps = UA_String_new();
+    *caps = UA_String_fromChars("LDS");
+    config.serverCapabilities = caps;
     /* timeout in seconds when to automatically remove a registered server from
      * the list, if it doesn't re-register within the given time frame. A value
      * of 0 disables automatic removal. Default is 60 Minutes (60*60). Must be
@@ -37,7 +43,9 @@ int main(void) {
 
     UA_StatusCode retval = UA_Server_run(server, &running);
     UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+    UA_Array_delete(config.serverCapabilities, config.serverCapabilitiesSize, &UA_TYPES[UA_TYPES_STRING]);
+    UA_String_deleteMembers(&config.mdnsServerName);
     UA_Server_delete(server);
     nl.deleteMembers(&nl);
-    return (int)retval;
+    return (int) retval;
 }

+ 192 - 0
examples/discovery/server_multicast.c

@@ -0,0 +1,192 @@
+/* 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.
+ * Compared to server_register.c this example waits until the LDS server announces
+ * itself through mDNS. Therefore the LDS server needs to support multicast extension
+ * (i.e., LDS-ME).
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "open62541.h"
+
+UA_Logger logger = UA_Log_Stdout;
+UA_Boolean running = true;
+
+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;
+}
+
+char *discovery_url = NULL;
+UA_String *self_discovery_url = NULL;
+
+static void
+serverOnNetworkCallback(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce,
+                        UA_Boolean isTxtReceived, void *data) {
+
+    if (discovery_url != NULL || !isServerAnnounce) {
+        UA_LOG_DEBUG(logger, UA_LOGCATEGORY_SERVER,
+                     "serverOnNetworkCallback called, but discovery URL "
+                     "already initialized or is not announcing. Ignoring.");
+        return; // we already have everything we need or we only want server announces
+    }
+
+    if (self_discovery_url != NULL && UA_String_equal(&serverOnNetwork->discoveryUrl, self_discovery_url)) {
+        // skip self
+        return;
+    }
+
+    if (!isTxtReceived)
+        return; // we wait until the corresponding TXT record is announced.
+                // Problem: how to handle if a Server does not announce the
+                // optional TXT?
+
+    // here you can filter for a specific LDS server, e.g. call FindServers on
+    // the serverOnNetwork to make sure you are registering with the correct
+    // LDS. We will ignore this for now
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Another server announced itself on %.*s",
+                serverOnNetwork->discoveryUrl.length, serverOnNetwork->discoveryUrl.data);
+
+    if (discovery_url != NULL)
+        free(discovery_url);
+    discovery_url = malloc(serverOnNetwork->discoveryUrl.length + 1);
+    memcpy(discovery_url, serverOnNetwork->discoveryUrl.data, serverOnNetwork->discoveryUrl.length);
+    discovery_url[serverOnNetwork->discoveryUrl.length] = 0;
+}
+
+int main(int argc, char **argv) {
+    signal(SIGINT, stopHandler); /* catches ctrl-c */
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    // To enable mDNS discovery, set application type to discovery server.
+    config.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
+    config.applicationDescription.applicationUri =
+        UA_String_fromChars("urn:open62541.example.server_multicast");
+    config.mdnsServerName = UA_String_fromChars("Sample Multicast Server");
+    // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
+    //config.serverCapabilitiesSize = 1;
+    //UA_String caps = UA_String_fromChars("LDS");
+    //config.serverCapabilities = &caps;
+    UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16665);
+    config.networkLayers = &nl;
+    config.networkLayersSize = 1;
+    UA_Server *server = UA_Server_new(config);
+    self_discovery_url = &nl.discoveryUrl;
+
+    /* 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;
+    dateDataSource.handle = &myInteger;
+    dateDataSource.read = readInteger;
+    dateDataSource.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);
+
+    // callback which is called when a new server is detected through mDNS
+    UA_Server_setServerOnNetworkCallback(server, serverOnNetworkCallback, NULL);
+
+    // Start the server and call iterate to wait for the multicast discovery of the LDS
+    UA_StatusCode retval = UA_Server_run_startup(server);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
+                     "Could not start the server. StatusCode %s",
+                     UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        free(discovery_url);
+        return 1;
+    }
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER,
+                "Server started. Waiting for announce of LDS Server.");
+    while (running && discovery_url == NULL)
+        UA_Server_run_iterate(server, true);
+    if (!running) {
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        free(discovery_url);
+        return 1;
+    }
+    UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "LDS-ME server found on %s", discovery_url);
+
+    // periodic server register after 10 Minutes, delay first register for 500ms
+    retval = UA_Server_addPeriodicServerRegisterJob(server, discovery_url,
+                                                    10 * 60 * 1000, 500, NULL);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
+                     "Could not create periodic job for server register. StatusCode %s",
+                     UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        free(discovery_url);
+        return 1;
+    }
+
+    while (running)
+        UA_Server_run_iterate(server, true);
+
+    UA_Server_run_shutdown(server);
+
+    // UNregister the server from the discovery server.
+    retval = UA_Server_unregister_discovery(server, discovery_url);
+    //retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" );
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER,
+                     "Could not unregister server from discovery server. "
+                     "StatusCode %s", UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        free(discovery_url);
+        return (int) retval;
+    }
+
+    UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+    UA_String_deleteMembers(&config.mdnsServerName);
+    //UA_Array_delete(config.serverCapabilities, config.serverCapabilitiesSize, &UA_TYPES[UA_TYPES_STRING]);
+    UA_Server_delete(server);
+    nl.deleteMembers(&nl);
+    free(discovery_url);
+
+    return (int) retval;
+}

+ 43 - 109
examples/discovery/server_register.c

@@ -1,22 +1,21 @@
 /* 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).
+ * A simple server instance which registers with the discovery server (see server_lds.c).
  * Before shutdown it has to unregister itself.
  */
 
-#ifdef _MSC_VER
-# include <io.h> //access
-#else
-# include <unistd.h> //access
-#endif
+
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "open62541.h"
 
+#define DISCOVERY_SERVER_ENDPOINT "opc.tcp://localhost:4840"
+
 UA_Logger logger = UA_Log_Stdout;
 UA_Boolean running = true;
+
 static void stopHandler(int sign) {
     UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
     running = false;
@@ -26,100 +25,38 @@ 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]);
+    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",
+    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);
+    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;
+    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);
+    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 = (struct PeriodicServerRegisterJob*)malloc(sizeof(struct PeriodicServerRegisterJob));
-            newRetryJob->job = (UA_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) {
+int main(int argc, char **argv) {
     signal(SIGINT, stopHandler); /* catches ctrl-c */
+    signal(SIGTERM, stopHandler);
 
     UA_ServerConfig config = UA_ServerConfig_standard;
-    config.applicationDescription.applicationUri=UA_String_fromChars("open62541.example.server_register");
+    config.applicationDescription.applicationUri = UA_String_fromChars("urn:open62541.example.server_register");
+    config.mdnsServerName = UA_String_fromChars("Sample Server");
+    // See http://www.opcfoundation.org/UA/schemas/1.03/ServerCapabilities.csv
+    //config.serverCapabilitiesSize = 1;
+    //UA_String caps = UA_String_fromChars("LDS");
+    //config.serverCapabilities = &caps;
     UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
     config.networkLayers = &nl;
     config.networkLayersSize = 1;
@@ -135,8 +72,8 @@ int main(int argc, char** argv) {
     dateDataSource.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");
+    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),
@@ -144,45 +81,42 @@ int main(int argc, char** argv) {
                                         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;
-    job.type = UA_JOBTYPE_METHODCALL;
-    job.job.methodCall.data = NULL;
-    job.job.methodCall.method = periodicServerRegister;
-    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 = (struct PeriodicServerRegisterJob*)malloc(sizeof(struct PeriodicServerRegisterJob));
-    newRetryJob->job = (UA_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);
-
+    // periodic server register after 10 Minutes, delay first register for 500ms
+    UA_StatusCode retval = UA_Server_addPeriodicServerRegisterJob(server, DISCOVERY_SERVER_ENDPOINT, 10 * 60 * 1000, 500, NULL);
+    //UA_StatusCode retval = UA_Server_addPeriodicServerRegisterJob(server, "opc.tcp://localhost:4840", 10*60*1000, 500, NULL);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not create periodic job for server register. StatusCode %s", UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+        UA_Server_delete(server);
+        nl.deleteMembers(&nl);
+        return (int) retval;
+    }
 
-    UA_StatusCode retval = UA_Server_run(server, &running);
+    retval = UA_Server_run(server, &running);
     if (retval != UA_STATUSCODE_GOOD) {
-        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not start discovery server. StatusCode 0x%08x", retval);
+        UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not start the server. StatusCode %s", UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
         UA_Server_delete(server);
         nl.deleteMembers(&nl);
-        return (int)retval;
+        return (int) retval;
     }
 
     // UNregister the server from the discovery server.
-    retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" );
+    retval = UA_Server_unregister_discovery(server, DISCOVERY_SERVER_ENDPOINT);
+    //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_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not unregister server from discovery server. StatusCode %s", UA_StatusCode_name(retval));
+        UA_String_deleteMembers(&config.applicationDescription.applicationUri);
         UA_Server_delete(server);
         nl.deleteMembers(&nl);
-        return (int)retval;
+        return (int) retval;
     }
 
     UA_String_deleteMembers(&config.applicationDescription.applicationUri);
+    UA_String_deleteMembers(&config.mdnsServerName);
+    //UA_Array_delete(config.serverCapabilities, config.serverCapabilitiesSize, &UA_TYPES[UA_TYPES_STRING]);
     UA_Server_delete(server);
     nl.deleteMembers(&nl);
 
-    return (int)retval;
+    return (int) retval;
 }

+ 4 - 0
examples/server_mainloop.c

@@ -41,6 +41,10 @@ int main(int argc, char** argv) {
         /* timeout is the maximum possible delay (in millisec) until the next
            _iterate call. Otherwise, the server might miss an internal timeout
            or cannot react to messages with the promised responsiveness. */
+        /* If multicast discovery server is enabled, the timeout does not not consider new input data (requests) on the mDNS socket.
+         * It will be handled on the next call, which may be too late for requesting clients.
+         * if needed, the select with timeout on the multicast socket server->mdnsSocket (see example in mdnsd library)
+         */
         UA_UInt16 timeout = UA_Server_run_iterate(server, waitInternal);
 
         /* Now we can use the max timeout to do something else. In this case, we

+ 2 - 2
examples/tutorial_datatypes.c

@@ -55,7 +55,7 @@ variables_basic(void) {
 
     cr.requestHeader.timestamp = UA_DateTime_now(); /* Members of a structure */
 
-    cr.methodsToCall = UA_Array_new(5, &UA_TYPES[UA_TYPES_CALLMETHODREQUEST]);
+    cr.methodsToCall = (UA_CallMethodRequest *)UA_Array_new(5, &UA_TYPES[UA_TYPES_CALLMETHODREQUEST]);
     cr.methodsToCallSize = 5; /* Array size needs to be made known */
 
     UA_CallRequest *cr2 = UA_CallRequest_new();
@@ -120,7 +120,7 @@ variables_variants(void) {
     UA_Variant_setArrayCopy(&v3, d, 9, &UA_TYPES[UA_TYPES_DOUBLE]);
 
     /* Set array dimensions */
-    v3.arrayDimensions = UA_Array_new(2, &UA_TYPES[UA_TYPES_UINT32]);
+    v3.arrayDimensions = (UA_UInt32 *)UA_Array_new(2, &UA_TYPES[UA_TYPES_UINT32]);
     v3.arrayDimensionsSize = 2;
     v3.arrayDimensions[0] = 3;
     v3.arrayDimensions[1] = 3;

+ 1 - 1
examples/tutorial_server_method.c

@@ -41,7 +41,7 @@ helloWorldMethodCallback(void *handle, const UA_NodeId *objectId,
     UA_String *inputStr = (UA_String*)input->data;
     UA_String tmp = UA_STRING_ALLOC("Hello ");
     if(inputStr->length > 0) {
-        tmp.data = realloc(tmp.data, tmp.length + inputStr->length);
+        tmp.data = (UA_Byte *)realloc(tmp.data, tmp.length + inputStr->length);
         memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
         tmp.length += inputStr->length;
     }

+ 65 - 16
include/ua_client.h

@@ -52,7 +52,7 @@ typedef struct UA_ClientConfig {
     const UA_DataType *customDataTypes;
 } UA_ClientConfig;
 
-/**
+/*
  * Client Lifecycle
  * ---------------- */
 typedef enum {
@@ -72,7 +72,7 @@ typedef enum {
 struct UA_Client;
 typedef struct UA_Client UA_Client;
 
-/* Create a new client
+/** Create a new client
  *
  * @param config for the new client. You can use UA_ClientConfig_standard
  *        which has sane defaults
@@ -81,32 +81,81 @@ typedef struct UA_Client UA_Client;
  * @return return the new Client object */
 UA_Client UA_EXPORT * UA_Client_new(UA_ClientConfig config);
 
-/* Get the client connection status */
+/** Get the client connection status */
 UA_ClientState UA_EXPORT UA_Client_getState(UA_Client *client);
 
-/* Reset a client */
+/** Reset a client */
 void UA_EXPORT UA_Client_reset(UA_Client *client);
 
-/* Delete a client */
+/** Delete a client */
 void UA_EXPORT UA_Client_delete(UA_Client *client);
 
-/**
- * Manage the Connection
- * --------------------- */
-/* Gets a list of endpoints of a server
+/** Gets a list of endpoints of a server
  *
- * @param client to use
- * @param server url to connect (for example "opc.tcp://localhost:16664")
+ * @param client to use. Must be connected to the same endpoint given in serverUrl or otherwise in disconnected state.
+ * @param serverUrl url to connect (for example "opc.tcp://localhost:16664")
  * @param endpointDescriptionsSize size of the array of endpoint descriptions
  * @param endpointDescriptions array of endpoint descriptions that is allocated
  *        by the function (you need to free manually)
- * @return Indicates whether the operation succeeded or returns an error code */
+ * @return Indicates whether the operation succeeded or returns an error code
+ */
 UA_StatusCode UA_EXPORT
 UA_Client_getEndpoints(UA_Client *client, const char *serverUrl,
                        size_t* endpointDescriptionsSize,
                        UA_EndpointDescription** endpointDescriptions);
 
-/* Connect to the selected server
+/**
+ * Gets a list of all registered servers at the given server.
+ *
+ * You can pass an optional filter for serverUris. If the given server is not registered,
+ * an empty array will be returned. If the server is registered, only that application
+ * description will be returned.
+ *
+ * Additionally you can optionally indicate which locale you want for the server name
+ * in the returned application description. The array indicates the order of preference.
+ * A server may have localized names.
+ *
+ * @param client to use. Must be connected to the same endpoint given in serverUrl or otherwise in disconnected state.
+ * @param serverUrl url to connect (for example "opc.tcp://localhost:16664")
+ * @param serverUrisSize Optional filter for specific server uris
+ * @param serverUris Optional filter for specific server uris
+ * @param localeIdsSize Optional indication which locale you prefer
+ * @param localeIds Optional indication which locale you prefer
+ * @param registeredServerSize size of returned array, i.e., number of found/registered servers
+ * @param registeredServers array containing found/registered servers
+ * @return Indicates whether the operation succeeded or returns an error code
+ */
+UA_StatusCode UA_EXPORT
+UA_Client_findServers(UA_Client *client, const char *serverUrl,
+                      size_t serverUrisSize, UA_String *serverUris,
+                      size_t localeIdsSize, UA_String *localeIds,
+                      size_t *registeredServerSize, UA_ApplicationDescription **registeredServers);
+
+/**
+ * Get a list of all known server in the network. Only supported by LDS servers.
+ *
+ *
+ * @param client to use. Must be connected to the same endpoint given in serverUrl or otherwise in disconnected state.
+ * @param serverUrl url to connect (for example "opc.tcp://localhost:16664")
+ * @param startingRecordId optional. Only return the records with an ID higher or equal the given. Can be used for pagination to only get a subset of the full list
+ * @param maxRecordsToReturn optional. Only return this number of records
+ * @param serverCapabilityFilterSize optional. Filter the returned list to only get servers with given capabilities, e.g. "LDS"
+ * @param serverCapabilityFilter optional. Filter the returned list to only get servers with given capabilities, e.g. "LDS"
+ * @param serverOnNetworkSize size of returned array, i.e., number of known/registered servers
+ * @param serverOnNetwork array containing known/registered servers
+ * @return Indicates whether the operation succeeded or returns an error code
+ */
+UA_StatusCode UA_EXPORT
+UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl,
+                     UA_UInt32 startingRecordId, UA_UInt32 maxRecordsToReturn,
+                     size_t serverCapabilityFilterSize, UA_String *serverCapabilityFilter,
+                     size_t *serverOnNetworkSize, UA_ServerOnNetwork **serverOnNetwork);
+
+/*
+ * Manage the Connection
+ * --------------------- */
+
+/** Connect to the selected server
  *
  * @param client to use
  * @param endpointURL to connect (for example "opc.tcp://localhost:16664")
@@ -114,7 +163,7 @@ UA_Client_getEndpoints(UA_Client *client, const char *serverUrl,
 UA_StatusCode UA_EXPORT
 UA_Client_connect(UA_Client *client, const char *endpointUrl);
 
-/* Connect to the selected server with the given username and password
+/** Connect to the selected server with the given username and password
  *
  * @param client to use
  * @param endpointURL to connect (for example "opc.tcp://localhost:16664")
@@ -125,10 +174,10 @@ UA_StatusCode UA_EXPORT
 UA_Client_connect_username(UA_Client *client, const char *endpointUrl,
                            const char *username, const char *password);
 
-/* Close a connection to the selected server */
+/** Close a connection to the selected server */
 UA_StatusCode UA_EXPORT UA_Client_disconnect(UA_Client *client);
 
-/* Renew the underlying secure channel */
+/** Renew the underlying secure channel */
 UA_StatusCode UA_EXPORT UA_Client_manuallyRenewSecureChannel(UA_Client *client);
 
 /**

+ 13 - 1
include/ua_config.h.in

@@ -25,7 +25,6 @@ extern "C" {
 #cmakedefine UA_ENABLE_METHODCALLS
 #cmakedefine UA_ENABLE_NODEMANAGEMENT
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS
-#cmakedefine UA_ENABLE_DISCOVERY
 #cmakedefine UA_ENABLE_MULTITHREADING
 
 /**
@@ -39,6 +38,9 @@ extern "C" {
 #cmakedefine UA_ENABLE_EXTERNAL_NAMESPACES
 #cmakedefine UA_ENABLE_NONSTANDARD_STATELESS
 #cmakedefine UA_ENABLE_NONSTANDARD_UDP
+#cmakedefine UA_ENABLE_DISCOVERY
+#cmakedefine UA_ENABLE_DISCOVERY_MULTICAST
+#cmakedefine UA_ENABLE_DISCOVERY_SEMAPHORE
 
 /**
  * Standard Includes
@@ -49,6 +51,16 @@ extern "C" {
 #ifndef _DEFAULT_SOURCE
 # define _DEFAULT_SOURCE
 #endif
+// On older systems we need to define _BSD_SOURCE
+// _DEFAULT_SOURCE is an alias for that
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
+// Disable deprecation warnings for sprintf, strncpy, strerror
+#ifdef _MSC_VER
+# define _CRT_SECURE_NO_WARNINGS
+#endif
 
 #include <stddef.h>
 #include "ms_stdint.h" /* Includes stdint.h or workaround for older Visual Studios */

+ 75 - 1
include/ua_server.h

@@ -156,6 +156,11 @@ typedef struct {
     UA_BuildInfo buildInfo;
     UA_ApplicationDescription applicationDescription;
     UA_ByteString serverCertificate;
+#ifdef UA_ENABLE_DISCOVERY
+    UA_String mdnsServerName;
+    size_t serverCapabilitiesSize;
+    UA_String *serverCapabilities;
+#endif
 
     /* Custom DataTypes */
     size_t customDataTypesSize;
@@ -601,7 +606,7 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
  * 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,
@@ -609,6 +614,10 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
   * for example a pid file which is deleted if the server crashes.
   *
   * When the server shuts down you need to call unregister.
+  *
+  * @param server
+  * @param discoveryServerUrl if set to NULL, the default value 'opc.tcp://localhost:4840' will be used
+  * @param semaphoreFilePath optional parameter pointing to semaphore file.
   */
  UA_StatusCode UA_EXPORT
  UA_Server_register_discovery(UA_Server *server, const char* discoveryServerUrl, const char* semaphoreFilePath);
@@ -616,9 +625,74 @@ UA_Server_forEachChildNodeCall(UA_Server *server, UA_NodeId parentNodeId,
  /**
   * Unregister the given server instance from the discovery server.
   * This should only be called when the server is shutting down.
+  * @param server
+  * @param discoveryServerUrl if set to NULL, the default value 'opc.tcp://localhost:4840' will be used
   */
  UA_StatusCode UA_EXPORT
  UA_Server_unregister_discovery(UA_Server *server, const char* discoveryServerUrl);
+
+ /**
+  * Adds a periodic job to register the server with the LDS (local discovery server)
+  * periodically. The interval between each register call is given as second parameter.
+  * It should be 10 minutes by default (= 10*60*1000).
+  *
+  * The delayFirstRegisterMs parameter indicates the delay for the first register call.
+  * If it is 0, the first register call will be after intervalMs milliseconds,
+  * otherwise the server's first register will be after delayFirstRegisterMs.
+  *
+  * When you manually unregister the server, you also need to cancel the periodic job,
+  * otherwise it will be automatically be registered again.
+  *
+  * @param server
+  * @param discoveryServerUrl if set to NULL, the default value 'opc.tcp://localhost:4840' will be used
+  * @param intervalMs
+  * @param delayFirstRegisterMs
+  * @param periodicJobId
+  */
+ UA_StatusCode UA_EXPORT
+         UA_Server_addPeriodicServerRegisterJob(UA_Server *server, const char* discoveryServerUrl, const UA_UInt32 intervalMs, const UA_UInt32 delayFirstRegisterMs, UA_Guid* periodicJobId);
+
+ /* Callback for RegisterServer. Data is passed from the register call */
+ typedef void (*UA_Server_registerServerCallback)(const UA_RegisteredServer *registeredServer, void* data);
+
+/**
+ * Set the callback which is called if another server registeres or unregisteres with this instance.
+ * If called multiple times, previous data will be overwritten.
+ * @param server
+ * @param cb the callback
+ * @param data data passed to the callback
+ * @return UA_STATUSCODE_SUCCESS on success
+ */
+void UA_EXPORT
+         UA_Server_setRegisterServerCallback(UA_Server *server,
+                                             UA_Server_registerServerCallback cb,
+                                             void* data);
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+
+/**
+ * Callback for server detected through mDNS. Data is passed from the register call
+ * @param isServerAnnounce indicates if the server has just been detected. If set to false, this means the server is shutting down.
+ * @param isTxtReceived indicates if we already received the corresponding TXT record with the path and caps data
+ **/
+typedef void (*UA_Server_serverOnNetworkCallback)(const UA_ServerOnNetwork *serverOnNetwork, UA_Boolean isServerAnnounce, UA_Boolean isTxtReceived, void* data);
+
+/**
+ * Set the callback which is called if another server is found through mDNS or deleted.
+ * It will be called for any mDNS message from the remote server, thus it may be called multiple times for the same instance.
+ * Also the SRV and TXT records may arrive later, therefore for the first call the server capabilities may not be set yet.
+ * If called multiple times, previous data will be overwritten.
+ * @param server
+ * @param cb the callback
+ * @param data data passed to the callback
+ * @return UA_STATUSCODE_SUCCESS on success
+ */
+void UA_EXPORT
+        UA_Server_setServerOnNetworkCallback(UA_Server *server,
+                                             UA_Server_serverOnNetworkCallback cb,
+                                             void* data);
+#endif
+
 #endif
 
 /**

+ 6 - 1
plugins/ua_config_standard.c

@@ -67,11 +67,16 @@ const UA_EXPORT UA_ServerConfig UA_ServerConfig_standard = {
       UA_STRING_STATIC_NULL,
       0, NULL }, /* .applicationDescription */
     UA_STRING_STATIC_NULL, /* .serverCertificate */
+#ifdef UA_ENABLE_DISCOVERY
+    UA_STRING_STATIC_NULL, /* mdnsServerName */
+    0, /* serverCapabilitiesSize */
+    NULL, /* serverCapabilities */
+#endif
 
     /* Custom DataTypes */
     0, /* .customDataTypesSize */
     NULL, /* .customDataTypes */
-    
+
     /* Networking */
     0, /* .networkLayersSize */
     NULL, /* .networkLayers */

+ 0 - 4
plugins/ua_network_tcp.c

@@ -24,10 +24,6 @@
 # ifndef __clang__
 #  include <malloc.h>
 # endif
-/* Fix redefinition of SLIST_ENTRY on mingw winnt.h */
-# ifdef SLIST_ENTRY
-#  undef SLIST_ENTRY
-# endif
 /* inet_ntoa is deprecated on MSVC but used for compatibility */
 # define _WINSOCK_DEPRECATED_NO_WARNINGS
 # include <winsock2.h>

+ 133 - 45
src/client/ua_client.c

@@ -556,45 +556,6 @@ static UA_StatusCode CloseSecureChannel(UA_Client *client) {
     return retval;
 }
 
-UA_StatusCode
-UA_Client_getEndpoints(UA_Client *client, const char *serverUrl,
-                       size_t* endpointDescriptionsSize,
-                       UA_EndpointDescription** endpointDescriptions) {
-    if(client->state == UA_CLIENTSTATE_CONNECTED)
-        return UA_STATUSCODE_GOOD;
-    if(client->state == UA_CLIENTSTATE_ERRORED)
-        UA_Client_reset(client);
-
-
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    client->connection =
-        client->config.connectionFunc(UA_ConnectionConfig_standard, serverUrl,
-                                      client->config.logger);
-    if(client->connection.state != UA_CONNECTION_OPENING) {
-        retval = UA_STATUSCODE_BADCONNECTIONCLOSED;
-        goto cleanup;
-    }
-
-    client->endpointUrl = UA_STRING_ALLOC(serverUrl);
-    if(!client->endpointUrl.data) {
-        retval = UA_STATUSCODE_BADOUTOFMEMORY;
-        goto cleanup;
-    }
-
-    client->connection.localConf = client->config.localConnectionConfig;
-    retval = HelAckHandshake(client);
-    if(retval == UA_STATUSCODE_GOOD)
-        retval = SecureChannelHandshake(client, false);
-    if(retval == UA_STATUSCODE_GOOD)
-        retval = GetEndpoints(client, endpointDescriptionsSize, endpointDescriptions);
-
-    /* always cleanup */
- cleanup:
-    UA_Client_disconnect(client);
-    UA_Client_reset(client);
-    return retval;
-}
-
 UA_StatusCode
 UA_Client_connect_username(UA_Client *client, const char *endpointUrl,
                            const char *username, const char *password){
@@ -604,9 +565,8 @@ UA_Client_connect_username(UA_Client *client, const char *endpointUrl,
     return UA_Client_connect(client, endpointUrl);
 }
 
-
-UA_StatusCode
-UA_Client_connect(UA_Client *client, const char *endpointUrl) {
+static UA_StatusCode
+_UA_Client_connect(UA_Client *client, const char *endpointUrl, UA_Boolean endpointsHandshake, UA_Boolean createSession) {
     if(client->state == UA_CLIENTSTATE_CONNECTED)
         return UA_STATUSCODE_GOOD;
     if(client->state == UA_CLIENTSTATE_ERRORED) {
@@ -632,11 +592,11 @@ UA_Client_connect(UA_Client *client, const char *endpointUrl) {
     retval = HelAckHandshake(client);
     if(retval == UA_STATUSCODE_GOOD)
         retval = SecureChannelHandshake(client, false);
-    if(retval == UA_STATUSCODE_GOOD)
+    if(endpointsHandshake && retval == UA_STATUSCODE_GOOD)
         retval = EndpointsHandshake(client);
-    if(retval == UA_STATUSCODE_GOOD)
+    if(endpointsHandshake && createSession && retval == UA_STATUSCODE_GOOD)
         retval = SessionHandshake(client);
-    if(retval == UA_STATUSCODE_GOOD)
+    if(endpointsHandshake && createSession && retval == UA_STATUSCODE_GOOD)
         retval = ActivateSession(client);
     if(retval == UA_STATUSCODE_GOOD) {
         client->connection.state = UA_CONNECTION_ESTABLISHED;
@@ -651,6 +611,11 @@ UA_Client_connect(UA_Client *client, const char *endpointUrl) {
     return retval;
 }
 
+UA_StatusCode
+UA_Client_connect(UA_Client *client, const char *endpointUrl) {
+    return _UA_Client_connect(client, endpointUrl, UA_TRUE, UA_TRUE);
+}
+
 UA_StatusCode UA_Client_disconnect(UA_Client *client) {
     if(client->state == UA_CLIENTSTATE_READY)
         return UA_STATUSCODE_BADNOTCONNECTED;
@@ -672,6 +637,129 @@ UA_StatusCode UA_Client_manuallyRenewSecureChannel(UA_Client *client) {
     return retval;
 }
 
+UA_StatusCode
+UA_Client_getEndpoints(UA_Client *client, const char *serverUrl,
+                       size_t* endpointDescriptionsSize,
+                       UA_EndpointDescription** endpointDescriptions) {
+    if (client->state == UA_CLIENTSTATE_CONNECTED &&
+        strncmp((const char*)client->endpointUrl.data, serverUrl, client->endpointUrl.length) != 0) {
+        // client is already connected but to a different endpoint url.
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_StatusCode retval = _UA_Client_connect(client, serverUrl, UA_FALSE, UA_FALSE);
+    if(retval == UA_STATUSCODE_GOOD)
+        retval = GetEndpoints(client, endpointDescriptionsSize, endpointDescriptions);
+
+    UA_Client_disconnect(client);
+    UA_Client_reset(client);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_findServers(UA_Client *client, const char *serverUrl,
+                      size_t serverUrisSize, UA_String *serverUris,
+                      size_t localeIdsSize, UA_String *localeIds,
+                      size_t *registeredServerSize, UA_ApplicationDescription **registeredServers) {
+
+    if (client->state == UA_CLIENTSTATE_CONNECTED &&
+       strncmp((const char*)client->endpointUrl.data, serverUrl, client->endpointUrl.length) != 0) {
+        // client is already connected but to a different endpoint url.
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_StatusCode retval = _UA_Client_connect(client, serverUrl, UA_TRUE, UA_FALSE);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_Client_disconnect(client);
+        UA_Client_reset(client);
+        return retval;
+    }
+
+    UA_FindServersRequest request;
+    UA_FindServersRequest_init(&request);
+
+    request.serverUrisSize = serverUrisSize;
+    request.serverUris = serverUris;
+
+    request.localeIdsSize = localeIdsSize;
+    request.localeIds = localeIds;
+
+    // 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]);
+    retval = response.responseHeader.serviceResult;
+    if (retval != UA_STATUSCODE_GOOD) {
+        *registeredServerSize = 0;
+        goto cleanup;
+    }
+
+    *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]);
+
+    /* always cleanup */
+    cleanup:
+    UA_FindServersResponse_deleteMembers(&response);
+    UA_Client_disconnect(client);
+    UA_Client_reset(client);
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl,
+                               UA_UInt32 startingRecordId, UA_UInt32 maxRecordsToReturn,
+                               size_t serverCapabilityFilterSize, UA_String *serverCapabilityFilter,
+                               size_t *serverOnNetworkSize, UA_ServerOnNetwork **serverOnNetwork) {
+
+    if (client->state == UA_CLIENTSTATE_CONNECTED &&
+        strncmp((const char*)client->endpointUrl.data, serverUrl, client->endpointUrl.length) != 0) {
+        // client is already connected but to a different endpoint url.
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+    }
+
+    UA_StatusCode retval = _UA_Client_connect(client, serverUrl, UA_TRUE, UA_FALSE);
+    if (retval != UA_STATUSCODE_GOOD) {
+        UA_Client_disconnect(client);
+        UA_Client_reset(client);
+        return retval;
+    }
+
+    UA_FindServersOnNetworkRequest request;
+    UA_FindServersOnNetworkRequest_init(&request);
+
+    request.startingRecordId = startingRecordId;
+    request.maxRecordsToReturn = maxRecordsToReturn;
+
+    request.serverCapabilityFilterSize = serverCapabilityFilterSize;
+    request.serverCapabilityFilter = serverCapabilityFilter;
+
+    // now send the request
+    UA_FindServersOnNetworkResponse response;
+    UA_FindServersOnNetworkResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_FINDSERVERSONNETWORKREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_FINDSERVERSONNETWORKRESPONSE]);
+    retval = response.responseHeader.serviceResult;
+    if (retval != UA_STATUSCODE_GOOD) {
+        *serverOnNetworkSize = 0;
+        goto cleanup;
+    }
+
+    *serverOnNetworkSize = response.serversSize;
+    *serverOnNetwork = (UA_ServerOnNetwork *) UA_Array_new(response.serversSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]);
+    for (size_t i = 0; i < response.serversSize; i++)
+        UA_ServerOnNetwork_copy(&response.servers[i], &(*serverOnNetwork)[i]);
+
+    cleanup:
+    UA_FindServersOnNetworkResponse_deleteMembers(&response);
+    UA_Client_disconnect(client);
+    UA_Client_reset(client);
+    return retval;
+}
+
 /****************/
 /* Raw Services */
 /****************/

+ 9 - 0
src/client/ua_client_internal.h

@@ -89,4 +89,13 @@ struct UA_Client {
 #endif
 };
 
+/* Connect to the selected server.
+ * This will not create a session.
+ *
+ * @param client to use
+ * @param endpointURL to connect (for example "opc.tcp://localhost:16664")
+ * @return Indicates whether the operation succeeded or returns an error code */
+UA_StatusCode UA_EXPORT
+UA_Client_connect_no_session(UA_Client *client, const char *endpointUrl);
+
 #endif /* UA_CLIENT_INTERNAL_H_ */

+ 519 - 0
src/server/ua_mdns.c

@@ -0,0 +1,519 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_server_internal.h"
+#include "ua_mdns_internal.h"
+#include "ua_util.h"
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+
+# ifdef UA_NO_AMALGAMATION
+#  include "mdnsd/libmdnsd/xht.h"
+#  include "mdnsd/libmdnsd/sdtxt.h"
+# endif
+#  ifdef _WIN32
+#   define _WINSOCK_DEPRECATED_NO_WARNINGS /* inet_ntoa is deprecated on MSVC but used for compatibility */
+#   include <winsock2.h>
+#   include <iphlpapi.h>
+#   include <ws2tcpip.h>
+#  else
+#   include <sys/time.h> // for struct timeval
+#   include <netinet/in.h> // for struct ip_mreq
+#   include <ifaddrs.h>
+#   include <net/if.h> /* for IFF_RUNNING */
+#   include <netdb.h> // for recvfrom in cygwin
+#  endif
+
+#ifndef STRDUP
+# if defined(__MINGW32__)
+static char *ua_strdup(const char *s) {
+    char *p = malloc(strlen(s) + 1);
+    if(p) { strcpy(p, s); }
+    return p;
+}
+# define STRDUP ua_strdup
+# elif defined(_WIN32)
+# define STRDUP _strdup
+# else
+# define STRDUP strdup
+# endif
+#endif
+
+// FIXME: Is this a required algorithm? Otherwise, reuse hashing for nodeids
+/* Generates a hash code for a string.
+ * This function uses the ELF hashing algorithm as reprinted in
+ * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996.
+ */
+static int mdns_hash_record(const char *s) {
+    /* ELF hash uses unsigned chars and unsigned arithmetic for portability */
+    const unsigned char *name = (const unsigned char *) s;
+    unsigned long h = 0;
+    while(*name) {
+        h = (h << 4) + (unsigned long) (*name++);
+        unsigned long g;
+        if((g = (h & 0xF0000000UL)) != 0)
+            h ^= (g >> 24);
+        h &= ~g;
+    }
+    return (int) h;
+}
+
+static struct serverOnNetwork_list_entry *
+mdns_record_add_or_get(UA_Server *server, const char *record, const char *serverName,
+                       size_t serverNameLen, UA_Boolean createNew) {
+    int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME;
+    struct serverOnNetwork_hash_entry *hash_entry = server->serverOnNetworkHash[hashIdx];
+
+    while (hash_entry) {
+        size_t maxLen;
+        if (serverNameLen > hash_entry->entry->serverOnNetwork.serverName.length)
+            maxLen = hash_entry->entry->serverOnNetwork.serverName.length;
+        else
+            maxLen = serverNameLen;
+
+        if (strncmp((char *) hash_entry->entry->serverOnNetwork.serverName.data, serverName, maxLen) == 0)
+            return hash_entry->entry;
+        hash_entry = hash_entry->next;
+    }
+
+    if(!createNew)
+        return NULL;
+
+    // not yet in list, create new one
+    // todo: malloc may fail: return a statuscode
+    struct serverOnNetwork_list_entry *listEntry =
+            (serverOnNetwork_list_entry *) malloc(sizeof(struct serverOnNetwork_list_entry));
+    listEntry->created = UA_DateTime_now();
+    listEntry->pathTmp = NULL;
+    listEntry->txtSet = UA_FALSE;
+    listEntry->srvSet = UA_FALSE;
+    UA_ServerOnNetwork_init(&listEntry->serverOnNetwork);
+    listEntry->serverOnNetwork.recordId = server->serverOnNetworkRecordIdCounter;
+    listEntry->serverOnNetwork.serverName.length = serverNameLen;
+    // todo: malloc may fail: return a statuscode
+    listEntry->serverOnNetwork.serverName.data = (UA_Byte *) malloc(serverNameLen);
+    memcpy(listEntry->serverOnNetwork.serverName.data, serverName, serverNameLen);
+    server->serverOnNetworkRecordIdCounter = UA_atomic_add(&server->serverOnNetworkRecordIdCounter, 1);
+    if (server->serverOnNetworkRecordIdCounter == 0)
+        server->serverOnNetworkRecordIdLastReset = UA_DateTime_now();
+
+    // add to hash
+    // todo: malloc may fail: return a statuscode
+    struct serverOnNetwork_hash_entry *newHashEntry =
+            (struct serverOnNetwork_hash_entry *) malloc(sizeof(struct serverOnNetwork_hash_entry));
+    newHashEntry->next = server->serverOnNetworkHash[hashIdx];
+    server->serverOnNetworkHash[hashIdx] = newHashEntry;
+    newHashEntry->entry = listEntry;
+
+    LIST_INSERT_HEAD(&server->serverOnNetwork, listEntry, pointers);
+
+    return listEntry;
+}
+
+static void
+mdns_record_remove(UA_Server *server, const char *record,
+                   struct serverOnNetwork_list_entry *entry) {
+    // remove from hash
+    int hashIdx = mdns_hash_record(record) % SERVER_ON_NETWORK_HASH_PRIME;
+    struct serverOnNetwork_hash_entry *hash_entry = server->serverOnNetworkHash[hashIdx];
+    struct serverOnNetwork_hash_entry *prevEntry = hash_entry;
+    while(hash_entry) {
+        if(hash_entry->entry == entry) {
+            if(server->serverOnNetworkHash[hashIdx] == hash_entry)
+                server->serverOnNetworkHash[hashIdx] = hash_entry->next;
+            else if(prevEntry)
+                prevEntry->next = hash_entry->next;
+            break;
+        }
+        prevEntry = hash_entry;
+        hash_entry = hash_entry->next;
+    }
+    free(hash_entry);
+
+    if(server->serverOnNetworkCallback)
+        server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_FALSE,
+                                        entry->txtSet, server->serverOnNetworkCallbackData);
+
+    // remove from list
+    LIST_REMOVE(entry, pointers);
+    UA_ServerOnNetwork_deleteMembers(&entry->serverOnNetwork);
+    if(entry->pathTmp)
+        free(entry->pathTmp);
+
+#ifndef UA_ENABLE_MULTITHREADING
+    server->serverOnNetworkSize--;
+    UA_free(entry);
+#else
+    server->serverOnNetworkSize = uatomic_add_return(&server->serverOnNetworkSize, -1);
+    UA_Server_delayedFree(server, entry);
+#endif
+}
+
+static void
+mdns_append_path_to_url(UA_String *url, const char *path) {
+    size_t pathLen = strlen(path);
+    // todo: malloc may fail: return a statuscode
+    char *newUrl = (char *) malloc(url->length + pathLen);
+    memcpy(newUrl, url->data, url->length);
+    memcpy(newUrl + url->length, path, pathLen);
+    url->length = url->length + pathLen;
+    url->data = (UA_Byte *) newUrl;
+}
+
+static void
+setTxt(const struct resource *r,
+       struct serverOnNetwork_list_entry *entry) {
+    entry->txtSet = UA_TRUE;
+    xht_t *x = txt2sd(r->rdata, r->rdlength);
+    char *path = (char *) xht_get(x, "path");
+    char *caps = (char *) xht_get(x, "caps");
+
+    if(path && strlen(path) > 1) {
+        if (!entry->srvSet) {
+            /* txt arrived before SRV, thus cache path entry */
+            // todo: malloc in strdup may fail: return a statuscode
+            entry->pathTmp = STRDUP(path);
+        } else {
+            /* SRV already there and discovery URL set. Add path to discovery URL */
+            mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, path);
+        }
+    }
+
+    if(caps && strlen(caps) > 0) {
+        /* count comma in caps */
+        size_t capsCount = 1;
+        for(size_t i = 0; caps[i]; i++) {
+            if(caps[i] == ',')
+                capsCount++;
+        }
+
+        /* set capabilities */
+        entry->serverOnNetwork.serverCapabilitiesSize = capsCount;
+        entry->serverOnNetwork.serverCapabilities =
+            (UA_String *) UA_Array_new(capsCount, &UA_TYPES[UA_TYPES_STRING]);
+
+        for(size_t i = 0; i < capsCount; i++) {
+            char *nextStr = strchr(caps, ',');
+            size_t len = nextStr ? (size_t) (nextStr - caps) : strlen(caps);
+            entry->serverOnNetwork.serverCapabilities[i].length = len;
+            // todo: malloc may fail: return a statuscode
+            entry->serverOnNetwork.serverCapabilities[i].data = (UA_Byte *) malloc(len);
+            memcpy(entry->serverOnNetwork.serverCapabilities[i].data, caps, len);
+            if (nextStr)
+                caps = nextStr + 1;
+            else
+                break;
+        }
+    }
+    xht_free(x);
+}
+
+// [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].
+static void
+setSrv(UA_Server *server, const struct resource *r,
+       struct serverOnNetwork_list_entry *entry) {
+    entry->srvSet = UA_TRUE;
+
+    // opc.tcp://[servername]:[port][path]
+    size_t srvNameLen = strlen(r->known.srv.name);
+    if(srvNameLen > 0 && r->known.srv.name[srvNameLen - 1] == '.')
+        srvNameLen--;
+
+    // todo: malloc may fail: return a statuscode
+    char *newUrl = (char *) malloc(10 + srvNameLen + 8);
+    sprintf(newUrl, "opc.tcp://%.*s:%d", (int) srvNameLen,
+            r->known.srv.name, r->known.srv.port);
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                "Multicast DNS: found server: %s", newUrl);
+    entry->serverOnNetwork.discoveryUrl = UA_String_fromChars(newUrl);
+    free(newUrl);
+
+    if(entry->pathTmp) {
+        mdns_append_path_to_url(&entry->serverOnNetwork.discoveryUrl, entry->pathTmp);
+        free(entry->pathTmp);
+    }
+}
+
+
+/* This will be called by the mDNS library on every record which is received */
+void mdns_record_received(const struct resource *r, void *data) {
+    UA_Server *server = (UA_Server *) data;
+    /* we only need SRV and TXT records */
+    // TODO: remove magic number
+    if((r->clazz != QCLASS_IN && r->clazz != QCLASS_IN + 32768) ||
+       (r->type != QTYPE_SRV && r->type != QTYPE_TXT))
+        return;
+
+    /* we only handle '_opcua-tcp._tcp.' records */
+    char *opcStr = strstr(r->name, "_opcua-tcp._tcp.");
+    if(!opcStr)
+        return;
+
+    /* Compute the length of the servername */
+    size_t servernameLen = (size_t) (opcStr - r->name);
+    if(servernameLen == 0)
+        return;
+    servernameLen--; // remove point
+
+    /* Get entry */
+    struct serverOnNetwork_list_entry *entry =
+            mdns_record_add_or_get(server, r->name, r->name, servernameLen, r->ttl > 0);
+    if(!entry)
+        return;
+
+    /* Check that the ttl is positive */
+    if(r->ttl == 0) {
+        UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                    "Multicast DNS: remove server (TTL=0): %.*s",
+                    entry->serverOnNetwork.discoveryUrl.length,
+                    entry->serverOnNetwork.discoveryUrl.data);
+        mdns_record_remove(server, r->name, entry);
+        return;
+    }
+
+    /* Update lastSeen */
+    entry->lastSeen = UA_DateTime_nowMonotonic();
+
+    /* TXT and SRV are already set */
+    if(entry->txtSet && entry->srvSet)
+        return;
+
+    /* Add the resources */
+    if(r->type == QTYPE_TXT && !entry->txtSet)
+        setTxt(r, entry);
+    else if (r->type == QTYPE_SRV && !entry->srvSet)
+        setSrv(server, r, entry);
+
+    /* Call callback to announce a new server */
+    if(entry->srvSet && server->serverOnNetworkCallback)
+        server->serverOnNetworkCallback(&entry->serverOnNetwork, UA_TRUE,
+                                        entry->txtSet, server->serverOnNetworkCallbackData);
+}
+
+void mdns_create_txt(UA_Server *server, const char *fullServiceDomain, const char *path,
+                     const UA_String *capabilites, const size_t *capabilitiesSize,
+                     void (*conflict)(char *host, int type, void *arg)) {
+    mdns_record_t *r = mdnsd_unique(server->mdnsDaemon, fullServiceDomain, QTYPE_TXT,
+                                    600, conflict, server);
+    xht_t *h = xht_new(11);
+    char *allocPath = NULL;
+    if (!path || strlen(path) == 0) {
+        xht_set(h, "path", "/");
+    } else {
+        // path does not contain slash, so add it here
+        if (path[0] == '/')
+            // todo: malloc in strdup may fail: return a statuscode
+            allocPath = STRDUP(path);
+        else {
+            // todo: malloc may fail: return a statuscode
+            allocPath = (char *) malloc(strlen(path) + 2);
+            allocPath[0] = '/';
+            memcpy(allocPath + 1, path, strlen(path));
+            allocPath[strlen(path) + 1] = '\0';
+        }
+        xht_set(h, "path", allocPath);
+    }
+
+    // calculate max string length:
+    size_t capsLen = 0;
+    for (size_t i = 0; i < *capabilitiesSize; i++) {
+        // add comma or last \0
+        capsLen += capabilites[i].length + 1;
+    }
+
+    char *caps = NULL;
+    if(capsLen) {
+        // freed when xht_free is called
+        // todo: malloc may fail: return a statuscode
+        caps = (char *) malloc(sizeof(char) * capsLen);
+        size_t idx = 0;
+        for (size_t i = 0; i < *capabilitiesSize; i++) {
+            strncpy(caps + idx, (const char *) capabilites[i].data, capabilites[i].length);
+            idx += capabilites[i].length + 1;
+            caps[idx - 1] = ',';
+        }
+        caps[idx - 1] = '\0';
+
+        xht_set(h, "caps", caps);
+    } else {
+        xht_set(h, "caps", "NA");
+    }
+
+    int txtRecordLength;
+    unsigned char *packet = sd2txt(h, &txtRecordLength);
+    if (allocPath)
+        free(allocPath);
+    if (caps)
+        free(caps);
+    xht_free(h);
+    mdnsd_set_raw(server->mdnsDaemon, r, (char *) packet, (unsigned short) txtRecordLength);
+    free(packet);
+}
+
+mdns_record_t *
+mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type,
+                 const char *host, const char *rdname) {
+    mdns_record_t *r = mdnsd_get_published(mdnsDaemon, host);
+    if(!r)
+        return NULL;
+
+    // search for the record with the correct ptr hostname
+    while(r) {
+        const mdns_answer_t *data = mdnsd_record_data(r);
+        if(data->type == type && strcmp(data->rdname, rdname) == 0)
+            return r;
+        r = mdnsd_record_next(r);
+    }
+    return NULL;
+}
+
+/* set record in the given interface */
+static void
+mdns_set_address_record_if(UA_Server *server, const char *fullServiceDomain,
+                           const char *localDomain, char *addr, UA_UInt16 addr_len) {
+    // [servername]-[hostname]._opcua-tcp._tcp.local. A [ip].
+    mdns_record_t *r = mdnsd_shared(server->mdnsDaemon, fullServiceDomain, QTYPE_A, 600);
+    mdnsd_set_raw(server->mdnsDaemon, r, addr, addr_len);
+    
+    // [hostname]. A [ip].
+    r = mdnsd_shared(server->mdnsDaemon, localDomain, QTYPE_A, 600);
+    mdnsd_set_raw(server->mdnsDaemon, r, addr, addr_len);
+}
+
+/* Loop over network interfaces and run set_address_record on each */
+#ifdef _WIN32
+
+// see http://stackoverflow.com/a/10838854/869402
+static IP_ADAPTER_ADDRESSES *
+getInterfaces(UA_Server *server) {
+    IP_ADAPTER_ADDRESSES* adapter_addresses = NULL;
+
+    // Start with a 16 KB buffer and resize if needed - multiple attempts in
+    // case interfaces change while we are in the middle of querying them.
+    DWORD adapter_addresses_buffer_size = 16 * 1024;
+    for(size_t attempts = 0; attempts != 3; ++attempts) {
+        // todo: malloc may fail: return a statuscode
+        adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addresses_buffer_size);
+        DWORD error = GetAdaptersAddresses(AF_UNSPEC,
+                                           GAA_FLAG_SKIP_ANYCAST |
+                                           GAA_FLAG_SKIP_DNS_SERVER |
+                                           GAA_FLAG_SKIP_FRIENDLY_NAME,
+                                           NULL, adapter_addresses,
+                                           &adapter_addresses_buffer_size);
+
+        if(ERROR_SUCCESS == error) {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "GetAdaptersAddresses returned an error. "
+                         "Not setting mDNS A records.");
+            adapter_addresses = NULL;
+            break;
+        } else if (ERROR_BUFFER_OVERFLOW == error) {
+            // Try again with the new size
+            free(adapter_addresses);
+            adapter_addresses = NULL;
+            continue;
+        }
+        
+        /* Unexpected error */
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "GetAdaptersAddresses returned an unexpected error. "
+                     "Not setting mDNS A records.");
+        free(adapter_addresses);
+        adapter_addresses = NULL;
+        break;
+    }
+    return adapter_addresses;
+}
+
+void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
+                             const char *localDomain) {
+    IP_ADAPTER_ADDRESSES* adapter_addresses = getInterfaces(server);
+
+    /* Iterate through all of the adapters */
+    IP_ADAPTER_ADDRESSES* adapter = NULL;
+    for(; adapter != NULL; adapter = adapter->Next) {
+        /* Skip loopback adapters */
+        if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType)
+            continue;
+
+        // Parse all IPv4 and IPv6 addresses
+        IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress;
+        for(; NULL != address; address = address->Next) {
+            int family = address->Address.lpSockaddr->sa_family;
+            if(AF_INET == family) {
+                SOCKADDR_IN* ipv4 = (SOCKADDR_IN*)(address->Address.lpSockaddr); // IPv4
+                mdns_set_address_record_if(server, fullServiceDomain, localDomain,
+                                           (char *)&ipv4->sin_addr, 4);
+            }
+            /*else if (AF_INET6 == family) {
+            // IPv6
+            SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*)(address->Address.lpSockaddr);
+
+            char str_buffer[INET6_ADDRSTRLEN] = {0};
+            inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);
+
+            std::string ipv6_str(str_buffer);
+
+            // Detect and skip non-external addresses
+            bool is_link_local(false);
+            bool is_special_use(false);
+
+            if(0 == ipv6_str.find("fe")) {
+            char c = ipv6_str[2];
+            if (c == '8' || c == '9' || c == 'a' || c == 'b')
+            is_link_local = true;
+            } else if (0 == ipv6_str.find("2001:0:")) {
+            is_special_use = true;
+            }
+
+            if(!(is_link_local || is_special_use))
+            ipAddrs.mIpv6.push_back(ipv6_str);
+            }*/
+        }
+    }
+
+    /* Cleanup */
+    free(adapter_addresses);
+    adapter_addresses = NULL;
+}
+
+#else //_WIN32
+
+void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
+                             const char *localDomain) {
+    struct ifaddrs *ifaddr, *ifa;
+    if(getifaddrs(&ifaddr) == -1) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "getifaddrs returned an unexpected error. Not setting mDNS A records.");
+        return;
+    }
+
+    /* Walk through linked list, maintaining head pointer so we can free list later */
+    int n;
+    for(ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
+        if(!ifa->ifa_addr)
+            continue;
+
+        if((strcmp("lo", ifa->ifa_name) == 0) ||
+           !(ifa->ifa_flags & (IFF_RUNNING))||
+           !(ifa->ifa_flags & (IFF_MULTICAST)))
+            continue;
+
+        /* IPv4 */
+        if(ifa->ifa_addr->sa_family == AF_INET) {
+            struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr;
+            mdns_set_address_record_if(server, fullServiceDomain, localDomain,
+                                       (char*)&sa->sin_addr.s_addr, 4);
+        }
+
+        /* IPv6 not implemented yet */
+    }
+
+    /* Clean up */
+    freeifaddrs(ifaddr);
+}
+
+#endif //_WIN32
+
+#endif // UA_ENABLE_DISCOVERY_MULTICAST

+ 51 - 0
src/server/ua_mdns_internal.h

@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef UA_MDNS_INTERNAL_H
+#define UA_MDNS_INTERNAL_H
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+
+/**
+ * TXT record:
+ * [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,...
+ *
+ * A/AAAA record for all ip addresses:
+ * [servername]-[hostname]._opcua-tcp._tcp.local. A [ip].
+ * [hostname]. A [ip].
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mdnsd/libmdnsd/mdnsd.h"
+
+void mdns_record_received(const struct resource *r, void *data);
+
+void mdns_create_txt(UA_Server *server, const char *fullServiceDomain,
+                     const char *path, const UA_String *capabilites,
+                     const size_t *capabilitiesSize,
+                     void (*conflict)(char *host, int type, void *arg));
+
+void mdns_set_address_record(UA_Server *server, const char *fullServiceDomain,
+                             const char *localDomain);
+
+mdns_record_t *
+mdns_find_record(mdns_daemon_t *mdnsDaemon, unsigned short type,
+                 const char *host, const char *rdname);
+
+void
+UA_Discovery_update_MdnsForDiscoveryUrl(UA_Server *server, const char *serverName,
+                                        UA_MdnsDiscoveryConfiguration *mdnsConfig,
+                                        const UA_String discoveryUrl, UA_Boolean isOnline,
+                                        UA_Boolean updateTxt);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UA_ENABLE_DISCOVERY_MULTICAST
+
+#endif //UA_MDNS_INTERNAL_H

+ 128 - 25
src/server/ua_server.c

@@ -260,12 +260,43 @@ void UA_Server_delete(UA_Server *server) {
                     &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);
+    {
+        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);
+        }
+    }
+    if (server->periodicServerRegisterJob) {
+        UA_free(server->periodicServerRegisterJob);
     }
+
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    if (server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER)
+        UA_Discovery_multicastDestroy(server);
+
+    {
+        serverOnNetwork_list_entry* current, * temp;
+        LIST_FOREACH_SAFE(current, &server->serverOnNetwork, pointers, temp) {
+            LIST_REMOVE(current, pointers);
+            UA_ServerOnNetwork_deleteMembers(&current->serverOnNetwork);
+            if (current->pathTmp)
+                free(current->pathTmp);
+            UA_free(current);
+        }
+
+        for (size_t i=0; i<SERVER_ON_NETWORK_HASH_PRIME; i++) {
+            serverOnNetwork_hash_entry* currHash = server->serverOnNetworkHash[i];
+            while (currHash) {
+                serverOnNetwork_hash_entry* nextHash = currHash->next;
+                free(currHash);
+                currHash = nextHash;
+            }
+        }
+    }
+# endif
+
 #endif
 
 #ifdef UA_ENABLE_MULTITHREADING
@@ -590,7 +621,7 @@ UA_Server * UA_Server_new(const UA_ServerConfig config) {
     UA_SessionManager_init(&server->sessionManager, server);
 
    UA_Job cleanup;
-   cleanup.type = UA_JOBTYPE_METHODCALL; 
+   cleanup.type = UA_JOBTYPE_METHODCALL;
    cleanup.job.methodCall.data = NULL;
    cleanup.job.methodCall.method = UA_Server_cleanup;
     UA_Server_addRepeatedJob(server, cleanup, 10000, NULL);
@@ -599,6 +630,26 @@ UA_Server * UA_Server_new(const UA_ServerConfig config) {
     // Discovery service
     LIST_INIT(&server->registeredServers);
     server->registeredServersSize = 0;
+    server->periodicServerRegisterJob = NULL;
+    server->registerServerCallback = NULL;
+    server->registerServerCallbackData = NULL;
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    server->mdnsDaemon = NULL;
+    server->mdnsSocket = 0;
+    server->mdnsMainSrvAdded = UA_FALSE;
+    if (server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) {
+        UA_Discovery_multicastInit(server);
+    }
+
+    LIST_INIT(&server->serverOnNetwork);
+    server->serverOnNetworkSize = 0;
+    server->serverOnNetworkRecordIdCounter = 0;
+    server->serverOnNetworkRecordIdLastReset = UA_DateTime_now();
+    memset(server->serverOnNetworkHash,0,sizeof(struct serverOnNetwork_hash_entry*)*SERVER_ON_NETWORK_HASH_PRIME);
+
+    server->serverOnNetworkCallback = NULL;
+    server->serverOnNetworkCallbackData = NULL;
+# endif
 #endif
 
     server->startTime = UA_DateTime_now();
@@ -1398,17 +1449,18 @@ register_server_with_discovery_server(UA_Server *server, const char* discoverySe
                                       const char* semaphoreFilePath) {
     /* Create the client */
     UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
-    UA_StatusCode retval = UA_Client_connect(client, discoveryServerUrl);
+    UA_StatusCode retval = UA_Client_connect(client, discoveryServerUrl != NULL ? discoveryServerUrl : "opc.tcp://localhost:4840");
     if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                     "Connecting to client failed with statuscode %s", UA_StatusCode_name(retval));
         UA_Client_delete(client);
         return retval;
     }
 
     /* Prepare the request. Do not cleanup the request after the service call,
      * as the members are stack-allocated or point into the server config. */
-    /* TODO: where do we get the discoveryProfileUri for application data? */
-    UA_RegisterServerRequest request;
-    UA_RegisterServerRequest_init(&request);
+    UA_RegisterServer2Request request;
+    UA_RegisterServer2Request_init(&request);
     request.requestHeader.timestamp = UA_DateTime_now();
     request.requestHeader.timeoutHint = 10000;
 
@@ -1418,8 +1470,14 @@ register_server_with_discovery_server(UA_Server *server, const char* discoverySe
     request.server.serverType = server->config.applicationDescription.applicationType;
     request.server.gatewayServerUri = server->config.applicationDescription.gatewayServerUri;
 
-    if(semaphoreFilePath)
+    if(semaphoreFilePath) {
+#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE
         request.server.semaphoreFilePath = UA_STRING((char*)(uintptr_t)semaphoreFilePath); /* dirty cast */
+#else
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                       "Ignoring semaphore file path. open62541 not compiled with UA_ENABLE_DISCOVERY_SEMAPHORE=ON");
+#endif
+    }
 
     request.server.serverNames = &server->config.applicationDescription.applicationName;
     request.server.serverNamesSize = 1;
@@ -1439,24 +1497,69 @@ register_server_with_discovery_server(UA_Server *server, const char* discoverySe
         request.server.discoveryUrls[config_discurls + i] = nl->discoveryUrl;
     }
 
-    /* Call the service */
-    UA_RegisterServerResponse response;
-    UA_RegisterServerResponse_init(&response);
-    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST],
-                        &response, &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE]);
+    UA_MdnsDiscoveryConfiguration mdnsConfig;
+    UA_MdnsDiscoveryConfiguration_init(&mdnsConfig);
 
-    /* Test the result and log on error */
-    retval = response.responseHeader.serviceResult;
-    if(retval != UA_STATUSCODE_GOOD)
-        UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_CLIENT,
-                    "RegisterServer failed with statuscode %s",
-                    UA_StatusCode_name(response.responseHeader.serviceResult));
+    request.discoveryConfigurationSize = 0;
+    request.discoveryConfigurationSize = 1;
+    request.discoveryConfiguration = UA_ExtensionObject_new();
+    UA_ExtensionObject_init(&request.discoveryConfiguration[0]);
+    request.discoveryConfiguration[0].encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
+    request.discoveryConfiguration[0].content.decoded.type = &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION];
+    request.discoveryConfiguration[0].content.decoded.data = &mdnsConfig;
+
+    mdnsConfig.mdnsServerName = server->config.mdnsServerName;
+    mdnsConfig.serverCapabilities = server->config.serverCapabilities;
+    mdnsConfig.serverCapabilitiesSize = server->config.serverCapabilitiesSize;
+
+    // First try with RegisterServer2, if that isn't implemented, use RegisterServer
+    UA_RegisterServer2Response response;
+    UA_RegisterServer2Response_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST],
+                        &response, &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE]);
+
+    UA_StatusCode serviceResult = response.responseHeader.serviceResult;
+    UA_RegisterServer2Response_deleteMembers(&response);
+
+    UA_ExtensionObject_delete(request.discoveryConfiguration);
+
+
+    if (serviceResult == UA_STATUSCODE_BADNOTIMPLEMENTED || serviceResult == UA_STATUSCODE_BADSERVICEUNSUPPORTED) {
+        // try RegisterServer
+        UA_RegisterServerResponse response_fallback;
+        UA_RegisterServerResponse_init(&response_fallback);
+
+        // copy from RegisterServer2 request
+        UA_RegisterServerRequest request_fallback;
+        UA_RegisterServerRequest_init(&request_fallback);
+
+        request_fallback.requestHeader = request.requestHeader;
+        request_fallback.server = request.server;
+
+        __UA_Client_Service(client, &request_fallback, &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST],
+                            &response_fallback, &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE]);
+
+        if(response_fallback.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                         "RegisterServer failed with statuscode %s", UA_StatusCode_name(response_fallback.responseHeader.serviceResult));
+            serviceResult = response_fallback.responseHeader.serviceResult;
+            UA_RegisterServerResponse_deleteMembers(&response_fallback);
+            UA_Client_disconnect(client);
+            UA_Client_delete(client);
+            return serviceResult;
+        }
+    } else if(serviceResult != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                     "RegisterServer2 failed with statuscode %s", UA_StatusCode_name(serviceResult));
+        UA_Client_disconnect(client);
+        UA_Client_delete(client);
+        return serviceResult;
+    }
 
-    /* Cleanup */
-    UA_RegisterServerResponse_deleteMembers(&response);
     UA_Client_disconnect(client);
     UA_Client_delete(client);
-    return retval;
+
+    return UA_STATUSCODE_GOOD;
 }
 
 UA_StatusCode

+ 15 - 0
src/server/ua_server_binary.c

@@ -53,10 +53,25 @@ getServicePointers(UA_UInt32 requestTypeId, const UA_DataType **requestType,
         *requiresSession = false;
         break;
 #ifdef UA_ENABLE_DISCOVERY
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    case UA_NS0ID_FINDSERVERSONNETWORKREQUEST_ENCODING_DEFAULTBINARY:
+        *service = (UA_Service)Service_FindServersOnNetwork;
+        *requestType = &UA_TYPES[UA_TYPES_FINDSERVERSONNETWORKREQUEST];
+        *responseType = &UA_TYPES[UA_TYPES_FINDSERVERSONNETWORKRESPONSE];
+        *requiresSession = false;
+        break;
+# endif
     case UA_NS0ID_REGISTERSERVERREQUEST_ENCODING_DEFAULTBINARY:
         *service = (UA_Service)Service_RegisterServer;
         *requestType = &UA_TYPES[UA_TYPES_REGISTERSERVERREQUEST];
         *responseType = &UA_TYPES[UA_TYPES_REGISTERSERVERRESPONSE];
+        *requiresSession = false;
+        break;
+    case UA_NS0ID_REGISTERSERVER2REQUEST_ENCODING_DEFAULTBINARY:
+        *service = (UA_Service)Service_RegisterServer2;
+        *requestType = &UA_TYPES[UA_TYPES_REGISTERSERVER2REQUEST];
+        *responseType = &UA_TYPES[UA_TYPES_REGISTERSERVER2RESPONSE];
+        *requiresSession = false;
         break;
 #endif
     case UA_NS0ID_CREATESESSIONREQUEST_ENCODING_DEFAULTBINARY:

+ 78 - 0
src/server/ua_server_internal.h

@@ -17,6 +17,10 @@ extern "C" {
 #include "ua_securechannel_manager.h"
 #include "ua_nodestore.h"
 
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+#include "mdnsd/libmdnsd/mdnsd.h"
+#endif
+
 #define ANONYMOUS_POLICY "open62541-anonymous-policy"
 #define USERNAME_POLICY "open62541-username-policy"
 
@@ -102,6 +106,27 @@ typedef struct registeredServer_list_entry {
     UA_RegisteredServer registeredServer;
     UA_DateTime lastSeen;
 } registeredServer_list_entry;
+
+
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+typedef struct serverOnNetwork_list_entry {
+    LIST_ENTRY(serverOnNetwork_list_entry) pointers;
+    UA_ServerOnNetwork serverOnNetwork;
+    UA_DateTime created;
+    UA_DateTime lastSeen;
+    UA_Boolean txtSet;
+    UA_Boolean srvSet;
+    char* pathTmp;
+} serverOnNetwork_list_entry;
+
+
+#define SERVER_ON_NETWORK_HASH_PRIME 1009
+typedef struct serverOnNetwork_hash_entry {
+    serverOnNetwork_list_entry* entry;
+    struct serverOnNetwork_hash_entry* next;
+} serverOnNetwork_hash_entry;
+#endif
+
 #endif
 
 struct UA_Server {
@@ -121,6 +146,29 @@ struct UA_Server {
     /* Discovery */
     LIST_HEAD(registeredServer_list, registeredServer_list_entry) registeredServers; // doubly-linked list of registered servers
     size_t registeredServersSize;
+    struct PeriodicServerRegisterJob *periodicServerRegisterJob;
+    UA_Server_registerServerCallback registerServerCallback;
+    void* registerServerCallbackData;
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    mdns_daemon_t *mdnsDaemon;
+    int mdnsSocket;
+    UA_Boolean mdnsMainSrvAdded;
+#  ifdef UA_ENABLE_MULTITHREADING
+    pthread_t mdnsThread;
+    UA_Boolean mdnsRunning;
+#  endif
+
+    LIST_HEAD(serverOnNetwork_list, serverOnNetwork_list_entry) serverOnNetwork; // doubly-linked list of servers on the network (from mDNS)
+    size_t serverOnNetworkSize;
+    UA_UInt32 serverOnNetworkRecordIdCounter;
+    UA_DateTime serverOnNetworkRecordIdLastReset;
+    // hash mapping domain name to serverOnNetwork list entry
+    struct serverOnNetwork_hash_entry* serverOnNetworkHash[SERVER_ON_NETWORK_HASH_PRIME];
+
+    UA_Server_serverOnNetworkCallback serverOnNetworkCallback;
+    void* serverOnNetworkCallbackData;
+
+# endif
 #endif
 
     size_t namespacesSize;
@@ -291,6 +339,36 @@ void Service_Call_single(UA_Server *server, UA_Session *session,
 /* Periodic task to clean up the discovery registry */
 void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime nowMonotonic);
 
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+
+UA_StatusCode UA_Discovery_multicastInit(UA_Server* server);
+void UA_Discovery_multicastDestroy(UA_Server* server);
+
+typedef enum {
+    UA_DISCOVERY_TCP,     /* OPC UA TCP mapping */
+    UA_DISCOVERY_TLS     /* OPC UA HTTPS mapping */
+} UA_DiscoveryProtocol;
+
+UA_StatusCode
+UA_Discovery_multicastQuery(UA_Server* server);
+
+UA_StatusCode
+UA_Discovery_addRecord(UA_Server* server, const char* servername, const char* hostname,
+                       unsigned short port, const char* path,
+                       const UA_DiscoveryProtocol protocol, UA_Boolean createTxt,
+                       const UA_String* capabilites, const size_t *capabilitiesSize);
+UA_StatusCode
+UA_Discovery_removeRecord(UA_Server* server, const char* servername, const char* hostname,
+                          unsigned short port, UA_Boolean removeTxt);
+
+#  ifdef UA_ENABLE_MULTITHREADING
+UA_StatusCode UA_Discovery_multicastListenStart(UA_Server* server);
+UA_StatusCode UA_Discovery_multicastListenStop(UA_Server* server);
+#  endif
+UA_StatusCode UA_Discovery_multicastIterate(UA_Server* server, UA_DateTime *nextRepeat, UA_Boolean processIn);
+
+# endif
+
 #ifdef __cplusplus
 } // extern "C"
 #endif

+ 102 - 0
src/server/ua_server_worker.c

@@ -40,6 +40,12 @@
  *     memory models." ACM SIGPLAN Notices. Vol. 48. No. 8. ACM, 2013.
  */
 
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+# ifndef _WIN32
+#  include <unistd.h> // gethostname
+# endif
+#endif
+
 void
 UA_Server_processJob(UA_Server *server, UA_Job *job) {
     UA_ASSERT_RCU_UNLOCKED();
@@ -313,6 +319,33 @@ static void processMainLoopJobs(UA_Server *server) {
 }
 #endif
 
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+static UA_StatusCode
+UA_Server_addMdnsRecordForNetworkLayer(UA_Server *server, const char* appName, const UA_ServerNetworkLayer* nl) {
+    UA_UInt16 port = 0;
+    char hostname[256]; hostname[0] = '\0';
+    const char *path;
+    {
+        char* uri = (char *)malloc(sizeof(char) * nl->discoveryUrl.length + 1);
+        strncpy(uri, (char*) nl->discoveryUrl.data, nl->discoveryUrl.length);
+        uri[nl->discoveryUrl.length] = '\0';
+        UA_StatusCode retval;
+        if ((retval = UA_EndpointUrl_split(uri, hostname, &port, &path)) != UA_STATUSCODE_GOOD) {
+            if (retval == UA_STATUSCODE_BADOUTOFRANGE)
+                UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_NETWORK, "Server url is invalid", uri);
+            else if (retval == UA_STATUSCODE_BADATTRIBUTEIDINVALID)
+                UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_NETWORK, "Server url '%s' does not begin with opc.tcp://", uri);
+            free(uri);
+            return UA_STATUSCODE_BADINVALIDARGUMENT;
+        }
+        free(uri);
+    }
+    UA_Discovery_addRecord(server, appName, hostname, port, path != NULL && strlen(path) ? path : "", UA_DISCOVERY_TCP, UA_TRUE,
+                           server->config.serverCapabilities, &server->config.serverCapabilitiesSize);
+    return UA_STATUSCODE_GOOD;
+}
+#endif //UA_ENABLE_DISCOVERY_MULTICAST
+
 UA_StatusCode UA_Server_run_startup(UA_Server *server) {
 #ifdef UA_ENABLE_MULTITHREADING
     /* Spin up the worker threads */
@@ -344,6 +377,33 @@ UA_StatusCode UA_Server_run_startup(UA_Server *server) {
         result |= nl->start(nl, server->config.logger);
     }
 
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    if (server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) {
+
+        char *appName = (char *)malloc(server->config.mdnsServerName.length +1);
+        memcpy(appName, server->config.mdnsServerName.data, server->config.mdnsServerName.length);
+        appName[server->config.mdnsServerName.length] = '\0';
+
+        for(size_t i = 0; i < server->config.networkLayersSize; i++) {
+            UA_StatusCode retVal = UA_Server_addMdnsRecordForNetworkLayer(
+                    server, appName, &server->config.networkLayers[i]);
+            if (UA_STATUSCODE_GOOD != retVal) {
+                free(appName);
+                return retVal;
+            }
+        }
+        free(appName);
+
+        // find any other server on the net
+        UA_Discovery_multicastQuery(server);
+
+# ifdef UA_ENABLE_MULTITHREADING
+        UA_Discovery_multicastListenStart(server);
+# endif
+    }
+#endif //UA_ENABLE_DISCOVERY_MULTICAST
+
     return result;
 }
 
@@ -436,6 +496,22 @@ UA_UInt16 UA_Server_run_iterate(UA_Server *server, UA_Boolean waitInternal) {
     processDelayedCallbacks(server);
 #endif
 
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+# ifndef UA_ENABLE_MULTITHREADING
+    if (server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) {
+        UA_DateTime multicastNextRepeat;
+        UA_DateTime_init(&multicastNextRepeat);
+        //TODO multicastNextRepeat does not consider new input data (requests) on the socket. It will be handled on the next call.
+        // if needed, we need to use select with timeout on the multicast socket server->mdnsSocket (see example in mdnsd library) on higher level.
+        if (UA_Discovery_multicastIterate(server, &multicastNextRepeat, UA_TRUE)) {
+            if (multicastNextRepeat < nextRepeated) {
+                UA_DateTime_copy(&multicastNextRepeat, &nextRepeated);
+            }
+        }
+    }
+# endif
+#endif
+
     now = UA_DateTime_nowMonotonic();
     timeout = 0;
     if(nextRepeated > now)
@@ -476,6 +552,32 @@ UA_StatusCode UA_Server_run_shutdown(UA_Server *server) {
 #else
     processDelayedCallbacks(server);
 #endif
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    if (server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) {
+        char* hostname = (char *)malloc(sizeof(char) * 256);
+        if (gethostname(hostname, 255) == 0) {
+            char *appName = (char *)malloc(server->config.mdnsServerName.length +1);
+            memcpy(appName, server->config.mdnsServerName.data, server->config.mdnsServerName.length);
+            appName[server->config.mdnsServerName.length] = '\0';
+            UA_Discovery_removeRecord(server,appName, hostname, 4840, UA_TRUE);
+            free(appName);
+        } else {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "Could not get hostname for multicast discovery.");
+        }
+        free(hostname);
+
+# ifdef UA_ENABLE_MULTITHREADING
+        UA_Discovery_multicastListenStop(server);
+# else
+        // send out last package with TTL = 0
+        UA_Discovery_multicastIterate(server, NULL, UA_FALSE);
+# endif
+    }
+
+#endif
+
     return UA_STATUSCODE_GOOD;
 }
 

+ 24 - 1
src/server/ua_services.h

@@ -42,6 +42,8 @@ typedef void (*UA_Service)(UA_Server*, UA_Session*,
  * ---------------------
  * This Service Set defines Services used to discover the Endpoints implemented
  * by a Server and to read the security configuration for those Endpoints. */
+/* Returns the Servers known to a Server or Discovery Server.
+ * The Client may reduce the number of results returned by specifying filter criteria */
 void Service_FindServers(UA_Server *server, UA_Session *session,
                          const UA_FindServersRequest *request,
                          UA_FindServersResponse *response);
@@ -53,11 +55,32 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session,
                           UA_GetEndpointsResponse *response);
 
 #ifdef UA_ENABLE_DISCOVERY
+
+# ifdef UA_ENABLE_DISCOVERY_MULTICAST
+/* Returns the Servers known to a Discovery Server. Unlike FindServer,
+ * this Service is only implemented by Discovery Servers. It additionally
+ * Returns servery which may have been detected trough Multicast */
+void Service_FindServersOnNetwork(UA_Server *server, UA_Session *session,
+                                  const UA_FindServersOnNetworkRequest *request,
+                                  UA_FindServersOnNetworkResponse *response);
+# endif // UA_ENABLE_DISCOVERY_MULTICAST
+
 /* 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);
-#endif
+
+/* Checks if a registration timed out and removes that registration.
+ * Should be called periodically in main loop */
+void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime nowMonotonic);
+
+/* This Service allows a Server to register its DiscoveryUrls and capabilities
+ * with a Discovery Server. It extends the registration information from
+ * RegisterServer with information necessary for FindServersOnNetwork. */
+void Service_RegisterServer2(UA_Server *server, UA_Session *session,
+                            const UA_RegisterServer2Request *request,
+                            UA_RegisterServer2Response *response);
+#endif // UA_ENABLE_DISCOVERY
 
 /**
  * SecureChannel Service Set

+ 419 - 129
src/server/ua_services_discovery.c

@@ -5,24 +5,27 @@
 #include "ua_server_internal.h"
 #include "ua_services.h"
 
+#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST)
+# include "ua_mdns_internal.h"
+#endif
 
-#ifdef UA_ENABLE_DISCOVERY
-    #ifdef _MSC_VER
-      #ifndef UNDER_CE
-        # include <io.h> //access
-        # define access _access
-      #endif
-    #else
-    # include <unistd.h> //access
-    #endif
+#ifdef _MSC_VER
+# ifndef UNDER_CE
+#  include <io.h> //access
+#  define access _access
+# endif
+#else
+# include <unistd.h> //access
 #endif
 
 #ifdef UA_ENABLE_DISCOVERY
-static UA_StatusCode copyRegisteredServerToApplicationDescription(const UA_FindServersRequest *request, UA_ApplicationDescription *target, const UA_RegisteredServer* registeredServer) {
+static UA_StatusCode
+setApplicationDescriptionFromRegisteredServer(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);
 
@@ -32,13 +35,20 @@ static UA_StatusCode copyRegisteredServerToApplicationDescription(const UA_FindS
         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);
+                    retval |= UA_LocalizedText_copy(&registeredServer->serverNames[j],
+                                                    &target->applicationName);
                     appNameFound = UA_TRUE;
                     break;
                 }
             }
         }
-    } else if (registeredServer->serverNamesSize){
+
+        // server does not have the requested local, therefore we can select the
+        // most suitable one
+        if(!appNameFound && registeredServer->serverNamesSize)
+            retval |= UA_LocalizedText_copy(&registeredServer->serverNames[0],
+                                            &target->applicationName);
+    } else if (registeredServer->serverNamesSize) {
         // just take the first name
         retval |= UA_LocalizedText_copy(&registeredServer->serverNames[0], &target->applicationName);
     }
@@ -48,48 +58,83 @@ static UA_StatusCode copyRegisteredServerToApplicationDescription(const UA_FindS
     // TODO where do we get the discoveryProfileUri for application data?
 
     target->discoveryUrlsSize = registeredServer->discoveryUrlsSize;
-    if (registeredServer->discoveryUrlsSize) {
-        target->discoveryUrls = (UA_String *)UA_malloc(sizeof(UA_String) * registeredServer->discoveryUrlsSize);
-        if (!target->discoveryUrls) {
+    if(registeredServer->discoveryUrlsSize) {
+        size_t duSize = sizeof(UA_String) * registeredServer->discoveryUrlsSize;
+        target->discoveryUrls = (UA_String *)UA_malloc(duSize);
+        if(!target->discoveryUrls)
             return UA_STATUSCODE_BADOUTOFMEMORY;
-        }
-        for (size_t i = 0; i<registeredServer->discoveryUrlsSize; i++) {
+        for(size_t i = 0; i<registeredServer->discoveryUrlsSize; i++)
             retval |= UA_String_copy(&registeredServer->discoveryUrls[i], &target->discoveryUrls[i]);
-        }
     }
 
     return retval;
 }
 #endif
 
+static UA_StatusCode
+setApplicationDescriptionFromServer(UA_ApplicationDescription *target, const UA_Server *server) {
+    /* Copy ApplicationDescription from the config */
+
+    UA_StatusCode result = UA_ApplicationDescription_copy(&server->config.applicationDescription,
+                                                          target);
+    if(result != UA_STATUSCODE_GOOD) {
+        return result;
+    }
+    // UaExpert does not list DiscoveryServer, thus set it to Server
+    // See http://forum.unified-automation.com/topic1987.html
+    if (target->applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER)
+        target->applicationType = UA_APPLICATIONTYPE_SERVER;
+
+    /* add the discoveryUrls from the networklayers */
+    size_t discSize = sizeof(UA_String) * (target->discoveryUrlsSize + server->config.networkLayersSize);
+    UA_String* disc = (UA_String *)UA_realloc(target->discoveryUrls, discSize);
+    if(!disc) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    size_t existing = target->discoveryUrlsSize;
+    target->discoveryUrls = disc;
+    target->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, &target->discoveryUrls[existing + i]);
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
 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");
+                         const UA_FindServersRequest *request,
+                         UA_FindServersResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing FindServersRequest");
 
     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
+    // 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_RegisteredServer **)UA_malloc(sizeof(UA_RegisteredServer*) * server->registeredServersSize);
+        size_t fsfpSize = sizeof(UA_RegisteredServer*) * server->registeredServersSize;
+        foundServerFilteredPointer = (UA_RegisteredServer **)UA_malloc(fsfpSize);
         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)) {
+        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])) {
+                    if(UA_String_equal(&current->registeredServer.serverUri, &request->serverUris[i])) {
                         foundServerFilteredPointer[foundServersSize++] = &current->registeredServer;
                         break;
                     }
@@ -97,19 +142,19 @@ void Service_FindServers(UA_Server *server, UA_Session *session,
             }
         }
 
-        if (addSelf)
+        if(addSelf)
             foundServersSize++;
 
     } else {
         addSelf = true;
-
         // self + registered servers
         foundServersSize = 1 + server->registeredServersSize;
     }
 #else
-    if (request->serverUrisSize) {
-        for (size_t i=0; i<request->serverUrisSize; i++) {
-            if (UA_String_equal(&request->serverUris[i], &server->config.applicationDescription.applicationUri)) {
+    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;
@@ -122,61 +167,40 @@ void Service_FindServers(UA_Server *server, UA_Session *session,
 #endif
 
     if(foundServersSize) {
-        foundServers = (UA_ApplicationDescription *)UA_malloc(sizeof(UA_ApplicationDescription) * foundServersSize);
-        if (!foundServers) {
-            if (foundServerFilteredPointer)
+        size_t fsSize = sizeof(UA_ApplicationDescription) * foundServersSize;
+        foundServers = (UA_ApplicationDescription *)UA_malloc(fsSize);
+        if(!foundServers) {
+            if(foundServerFilteredPointer)
                 UA_free(foundServerFilteredPointer);
             response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
             return;
         }
 
-        /* copy ApplicationDescription from the config */
         if(addSelf) {
-            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_String *)UA_realloc(foundServers[0].discoveryUrls,
-                                         sizeof(UA_String) * (foundServers[0].discoveryUrlsSize +
-                                                              server->config.networkLayersSize));
-            if(!disc) {
-                response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            response->responseHeader.serviceResult = setApplicationDescriptionFromServer(&foundServers[0], server);
+            if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
                 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
 
+#ifdef UA_ENABLE_DISCOVERY
         size_t currentIndex = 0;
         if (addSelf)
             currentIndex++;
 
         // add all the registered servers to the list
-        if(foundServerFilteredPointer) {
+
+        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++) {
+            for (size_t i = 0; i < iterCount; i++) {
                 response->responseHeader.serviceResult =
-                    copyRegisteredServerToApplicationDescription(request, &foundServers[currentIndex++],
-                                                                 foundServerFilteredPointer[i]);
+                        setApplicationDescriptionFromRegisteredServer(request, &foundServers[currentIndex++],
+                                                                      foundServerFilteredPointer[i]);
                 if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
                     UA_free(foundServers);
                     UA_free(foundServerFilteredPointer);
@@ -189,8 +213,8 @@ void Service_FindServers(UA_Server *server, UA_Session *session,
             registeredServer_list_entry* current;
             LIST_FOREACH(current, &server->registeredServers, pointers) {
                 response->responseHeader.serviceResult =
-                    copyRegisteredServerToApplicationDescription(request, &foundServers[currentIndex++],
-                                                                 &current->registeredServer);
+                        setApplicationDescriptionFromRegisteredServer(request, &foundServers[currentIndex++],
+                                                                      &current->registeredServer);
                 if (response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
                     UA_free(foundServers);
                     return;
@@ -207,24 +231,24 @@ void Service_FindServers(UA_Server *server, UA_Session *session,
     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,
                           UA_GetEndpointsResponse *response) {
     /* If the client expects to see a specific endpointurl, mirror it back. If
        not, clone the endpoints with the discovery url of all networklayers. */
     const UA_String *endpointUrl = &request->endpointUrl;
     if(endpointUrl->length > 0) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing GetEndpointsRequest with endpointUrl " \
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "Processing GetEndpointsRequest with endpointUrl "
                              UA_PRINTF_STRING_FORMAT, UA_PRINTF_STRING_DATA(*endpointUrl));
     } else {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing GetEndpointsRequest with an empty endpointUrl");
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "Processing GetEndpointsRequest with an empty endpointUrl");
     }
 
     /* test if the supported binary profile shall be returned */
-#ifdef NO_ALLOCA
-    UA_Boolean relevant_endpoints[server->endpointDescriptionsSize];
-#else
-    UA_Boolean *relevant_endpoints = (UA_Boolean *)UA_alloca(sizeof(UA_Boolean) * server->endpointDescriptionsSize);
-#endif
+    size_t reSize = sizeof(UA_Boolean) * server->endpointDescriptionsSize;
+    UA_Boolean *relevant_endpoints = (UA_Boolean *)UA_alloca(reSize);
     memset(relevant_endpoints, 0, sizeof(UA_Boolean) * server->endpointDescriptionsSize);
     size_t relevant_count = 0;
     if(request->profileUrisSize == 0) {
@@ -234,7 +258,8 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEn
     } else {
         for(size_t j = 0; j < server->endpointDescriptionsSize; ++j) {
             for(size_t i = 0; i < request->profileUrisSize; ++i) {
-                if(!UA_String_equal(&request->profileUris[i], &server->endpointDescriptions[j].transportProfileUri))
+                if(!UA_String_equal(&request->profileUris[i],
+                                    &server->endpointDescriptions[j].transportProfileUri))
                     continue;
                 relevant_endpoints[j] = true;
                 ++relevant_count;
@@ -256,7 +281,9 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEn
         nl_endpointurl = true;
     }
 
-    response->endpoints = (UA_EndpointDescription *)UA_Array_new(relevant_count * clone_times, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+    response->endpoints =
+        (UA_EndpointDescription*)UA_Array_new(relevant_count * clone_times,
+                                              &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
     if(!response->endpoints) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
@@ -279,7 +306,8 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEn
 
     if(retval != UA_STATUSCODE_GOOD) {
         response->responseHeader.serviceResult = retval;
-        UA_Array_delete(response->endpoints, response->endpointsSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+        UA_Array_delete(response->endpoints, response->endpointsSize,
+                        &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
         response->endpoints = NULL;
         response->endpointsSize = 0;
         return;
@@ -287,32 +315,114 @@ void Service_GetEndpoints(UA_Server *server, UA_Session *session, const UA_GetEn
 }
 
 #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");
 
+static void
+process_RegisterServer(UA_Server *server, UA_Session *session,
+                       const UA_RequestHeader* requestHeader,
+                       const UA_RegisteredServer *requestServer,
+                       const size_t requestDiscoveryConfigurationSize,
+                       const UA_ExtensionObject *requestDiscoveryConfiguration,
+                       UA_ResponseHeader* responseHeader,
+                       size_t *responseConfigurationResultsSize,
+                       UA_StatusCode **responseConfigurationResults,
+                       size_t *responseDiagnosticInfosSize,
+                       UA_DiagnosticInfo *responseDiagnosticInfos) {
+    /* Find the server from the request in the registered list */
+    registeredServer_list_entry* current;
     registeredServer_list_entry *registeredServer_entry = NULL;
+    LIST_FOREACH(current, &server->registeredServers, pointers) {
+        if (UA_String_equal(&current->registeredServer.serverUri, &requestServer->serverUri)) {
+            registeredServer_entry = current;
+            break;
+        }
+    }
 
-    {
-        // 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;
+    UA_MdnsDiscoveryConfiguration *mdnsConfig = NULL;
+
+    const UA_String* mdnsServerName = NULL;
+    if(requestDiscoveryConfigurationSize) {
+        *responseConfigurationResultsSize = requestDiscoveryConfigurationSize;
+        *responseConfigurationResults =
+            (UA_StatusCode *)UA_Array_new(requestDiscoveryConfigurationSize,
+                                          &UA_TYPES[UA_TYPES_STATUSCODE]);
+        for(size_t i =0; i<requestDiscoveryConfigurationSize; i++) {
+            const UA_ExtensionObject *object = &requestDiscoveryConfiguration[i];
+            if(!mdnsConfig && (object->encoding == UA_EXTENSIONOBJECT_DECODED ||
+                               object->encoding == UA_EXTENSIONOBJECT_DECODED_NODELETE) &&
+               (object->content.decoded.type == &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION])) {
+                mdnsConfig = (UA_MdnsDiscoveryConfiguration *)object->content.decoded.data;
+                mdnsServerName = &mdnsConfig->mdnsServerName;
+                *responseConfigurationResults[i] = UA_STATUSCODE_GOOD;
+            } else {
+                *responseConfigurationResults[i] = UA_STATUSCODE_BADNOTSUPPORTED;
             }
         }
     }
 
-    if (!request->server.isOnline) {
+    if(!mdnsServerName && requestServer->serverNamesSize)
+        mdnsServerName = &requestServer->serverNames[0].text;
+
+    if(!mdnsServerName) {
+        responseHeader->serviceResult = UA_STATUSCODE_BADSERVERNAMEMISSING;
+        return;
+    }
+
+    if(requestServer->discoveryUrlsSize == 0) {
+        responseHeader->serviceResult = UA_STATUSCODE_BADDISCOVERYURLMISSING;
+        return;
+    }
+
+    if(requestServer->semaphoreFilePath.length) {
+#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE
+        // todo: malloc may fail: return a statuscode
+        char* filePath = (char *)UA_malloc(sizeof(char)*requestServer->semaphoreFilePath.length+1);
+        memcpy(filePath, requestServer->semaphoreFilePath.data, requestServer->semaphoreFilePath.length );
+        filePath[requestServer->semaphoreFilePath.length] = '\0';
+        if(access( filePath, 0 ) == -1) {
+            responseHeader->serviceResult = UA_STATUSCODE_BADSEMPAHOREFILEMISSING;
+            free(filePath);
+            return;
+        }
+        free(filePath);
+#else
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_CLIENT,
+                       "Ignoring semaphore file path. open62541 not compiled with UA_ENABLE_DISCOVERY_SEMAPHORE=ON");
+#endif
+    }
+
+#ifdef UA_ENABLE_DISCOVERY_MULTICAST
+    if(server->config.applicationDescription.applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) {
+        // todo: malloc may fail: return a statuscode
+        char* mdnsServer = (char *)UA_malloc(sizeof(char) * mdnsServerName->length + 1);
+        memcpy(mdnsServer, mdnsServerName->data, mdnsServerName->length);
+        mdnsServer[mdnsServerName->length] = '\0';
+
+        for(size_t i = 0; i < requestServer->discoveryUrlsSize; i++) {
+            /* create TXT if is online and first index, delete TXT if is offline and last index */
+            UA_Boolean updateTxt = (requestServer->isOnline && i==0) ||
+                (!requestServer->isOnline && i==requestServer->discoveryUrlsSize);
+            UA_Discovery_update_MdnsForDiscoveryUrl(server, mdnsServer, mdnsConfig,
+                                                    requestServer->discoveryUrls[i],
+                                                    requestServer->isOnline, updateTxt);
+        }
+        free(mdnsServer);
+    }
+#endif
+
+    if(!requestServer->isOnline) {
         // server is shutting down. Remove it from the registered servers list
-        if (!registeredServer_entry) {
+        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;
+            UA_LOG_WARNING_SESSION(server->config.logger, session,
+                                   "Could not unregister server %.*s. Not registered.",
+                                   (int)requestServer->serverUri.length, requestServer->serverUri.data);
+            responseHeader->serviceResult = UA_STATUSCODE_BADNOTFOUND;
             return;
         }
 
+        if(server->registerServerCallback)
+            server->registerServerCallback(requestServer, server->registerServerCallbackData);
+
         // server found, remove from list
         LIST_REMOVE(registeredServer_entry, pointers);
         UA_RegisteredServer_deleteMembers(&registeredServer_entry->registeredServer);
@@ -323,21 +433,20 @@ void Service_RegisterServer(UA_Server *server, UA_Session *session,
         server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, -1);
         UA_Server_delayedFree(server, registeredServer_entry);
 #endif
-        response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
+        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)requestServer->serverUri.length, requestServer->serverUri.data);
 
-
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Registering new server: %.*s", (int)request->server.serverUri.length, request->server.serverUri.data);
-
-        registeredServer_entry = (registeredServer_list_entry *)UA_malloc(sizeof(registeredServer_list_entry));
+        registeredServer_entry =
+            (registeredServer_list_entry *)UA_malloc(sizeof(registeredServer_list_entry));
         if(!registeredServer_entry) {
-            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
             return;
         }
 
@@ -348,65 +457,88 @@ void Service_RegisterServer(UA_Server *server, UA_Session *session,
         server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, 1);
 #endif
 
+        if(server->registerServerCallback)
+            server->registerServerCallback(requestServer, server->registerServerCallbackData);
     } else {
         UA_RegisteredServer_deleteMembers(&registeredServer_entry->registeredServer);
     }
 
     // copy the data from the request into the list
-    UA_RegisteredServer_copy(&request->server, &registeredServer_entry->registeredServer);
+    UA_RegisteredServer_copy(requestServer, &registeredServer_entry->registeredServer);
     registeredServer_entry->lastSeen = UA_DateTime_nowMonotonic();
+    responseHeader->serviceResult = retval;
+}
 
-    response->responseHeader.serviceResult = retval;
+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");
+    process_RegisterServer(server, session, &request->requestHeader, &request->server, 0,
+                           NULL, &response->responseHeader, 0, NULL, 0, NULL);
 }
 
-/**
- * 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 nowMonotonic) {
+void Service_RegisterServer2(UA_Server *server, UA_Session *session,
+                            const UA_RegisterServer2Request *request,
+                             UA_RegisterServer2Response *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing RegisterServer2Request");
+    process_RegisterServer(server, session, &request->requestHeader, &request->server,
+                           request->discoveryConfigurationSize, request->discoveryConfiguration,
+                           &response->responseHeader, &response->configurationResultsSize,
+                           &response->configurationResults, &response->diagnosticInfosSize,
+                           response->diagnosticInfos);
+}
 
+/* 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 nowMonotonic) {
     UA_DateTime timedOut = nowMonotonic;
-    // registration is timed out if lastSeen is older than 60 minutes (default value, can be modified by user).
-    if (server->config.discoveryCleanupTimeout) {
+    // registration is timed out if lastSeen is older than 60 minutes (default
+    // value, can be modified by user).
+    if(server->config.discoveryCleanupTimeout)
         timedOut -= server->config.discoveryCleanupTimeout*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 = (char *)malloc(sizeof(char)*current->registeredServer.semaphoreFilePath.length+1);
-            memcpy( filePath, current->registeredServer.semaphoreFilePath.data, current->registeredServer.semaphoreFilePath.length );
+#ifdef UA_ENABLE_DISCOVERY_SEMAPHORE
+        if(current->registeredServer.semaphoreFilePath.length) {
+            size_t fpSize = sizeof(char)*current->registeredServer.semaphoreFilePath.length+1;
+            // todo: malloc may fail: return a statuscode
+            char* filePath = (char *)UA_malloc(fpSize);
+            memcpy(filePath, current->registeredServer.semaphoreFilePath.data,
+                   current->registeredServer.semaphoreFilePath.length );
             filePath[current->registeredServer.semaphoreFilePath.length] = '\0';
 #ifdef UNDER_CE
            FILE *fp = fopen(filePath,"rb");
            semaphoreDeleted = (fp==NULL);
-           if (fp)
+           if(fp)
              fclose(fp);
 #else
-            semaphoreDeleted = access( filePath, 0 ) == -1;
+           semaphoreDeleted = access( filePath, 0 ) == -1;
 #endif
-            free(filePath);
+           free(filePath);
         }
+#endif
 
-        if (semaphoreDeleted || (server->config.discoveryCleanupTimeout && current->lastSeen < timedOut)) {
-            if (semaphoreDeleted) {
+        if(semaphoreDeleted || (server->config.discoveryCleanupTimeout &&
+                                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);
+                            "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 {
                 // cppcheck-suppress unreadVariable
-                UA_String lastStr = UA_DateTime_toString(current->lastSeen);
                 UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
-                             "Registration of server with URI %.*s has timed out and is removed. Last seen: %.*s",
-                            (int)current->registeredServer.serverUri.length, current->registeredServer.serverUri.data,
-                            (int)lastStr.length, lastStr.data);
-                UA_free(lastStr.data);
+                            "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);
@@ -417,9 +549,167 @@ void UA_Discovery_cleanupTimedOut(UA_Server *server, UA_DateTime nowMonotonic) {
             server->registeredServersSize = uatomic_add_return(&server->registeredServersSize, -1);
             UA_Server_delayedFree(server, current);
 #endif
+        }
+    }
+}
+
+struct PeriodicServerRegisterJob {
+    UA_UInt32 default_interval;
+    UA_Guid job_id;
+    UA_Job *job;
+    UA_UInt32 this_interval;
+    const char* discovery_server_url;
+};
+
+/* 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 which holds information
+ * if the next interval is default or if it is a repeaded call. */
+static void
+periodicServerRegister(UA_Server *server, void *data) {
+    if(!data) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Data parameter must be not NULL for periodic server register");
+        return;
+    }
 
+    struct PeriodicServerRegisterJob *retryJob = (struct PeriodicServerRegisterJob *)data;
+    if(retryJob->job != NULL) {
+        // remove the retry job because we don't want to fire it again.
+        UA_Server_removeRepeatedJob(server, retryJob->job_id);
+    }
+
+    // fixme: remove magic urls
+    const char * server_url;
+    if(retryJob->discovery_server_url != NULL)
+        server_url = retryJob->discovery_server_url;
+    else
+        server_url = "opc.tcp://localhost:4840";
+    UA_StatusCode retval = UA_Server_register_discovery(server, server_url, 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(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Could not register server with discovery server. "
+                     "Is the discovery server started? StatusCode %s", UA_StatusCode_name(retval));
+
+        // first retry in 1 second
+        UA_UInt32 nextInterval = 1;
+
+        if (retryJob->job != NULL)
+            // double the interval for the next retry
+            nextInterval = retryJob->this_interval*2;
+
+        // as long as next retry is smaller than default interval, retry
+        if (nextInterval < retryJob->default_interval) {
+            UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                        "Retrying registration in %d seconds", nextInterval);
+            // todo: malloc may fail: return a statuscode
+            struct PeriodicServerRegisterJob *newRetryJob =
+                (struct PeriodicServerRegisterJob *)UA_malloc(sizeof(struct PeriodicServerRegisterJob));
+            newRetryJob->job = (UA_Job *)UA_malloc(sizeof(UA_Job));
+            newRetryJob->default_interval = retryJob->default_interval;
+            newRetryJob->this_interval = nextInterval;
+            newRetryJob->discovery_server_url = retryJob->discovery_server_url;
+
+            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_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
+                    "Server successfully registered. Next periodical register will be in %d seconds",
+                    (int)(retryJob->default_interval/1000));
+    }
+
+    if(retryJob->job) {
+        UA_free(retryJob->job);
+        UA_free(retryJob);
     }
+
 }
 
-#endif
+UA_StatusCode
+UA_Server_addPeriodicServerRegisterJob(UA_Server *server,
+                                       const char* discoveryServerUrl,
+                                       const UA_UInt32 intervalMs,
+                                       const UA_UInt32 delayFirstRegisterMs,
+                                       UA_Guid* periodicJobId) {
+    if(server->periodicServerRegisterJob != NULL)
+        return UA_STATUSCODE_BADINTERNALERROR;
+
+    // registering the server should be done periodically. Approx. every 10
+    // minutes. The first call will be in 10 Minutes.
+    UA_Job job;
+    job.type = UA_JOBTYPE_METHODCALL;
+    job.job.methodCall.method = periodicServerRegister;
+    job.job.methodCall.data = NULL;
+
+    server->periodicServerRegisterJob =
+        (struct PeriodicServerRegisterJob *)UA_malloc(sizeof(struct PeriodicServerRegisterJob));
+    server->periodicServerRegisterJob->job = NULL;
+    server->periodicServerRegisterJob->this_interval = 0;
+    server->periodicServerRegisterJob->default_interval = intervalMs;
+    server->periodicServerRegisterJob->discovery_server_url = discoveryServerUrl;
+    job.job.methodCall.data = server->periodicServerRegisterJob;
+
+    UA_StatusCode retval =
+        UA_Server_addRepeatedJob(server, job,
+                                 intervalMs, &server->periodicServerRegisterJob->job_id);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Could not create periodic job for server register. "
+                     "StatusCode %s", UA_StatusCode_name(retval));
+        return retval;
+    }
+
+    if(periodicJobId)
+        UA_Guid_copy(&server->periodicServerRegisterJob->job_id, periodicJobId);
+
+    if(delayFirstRegisterMs > 0) {
+        // Register the server with the discovery server.
+        // Delay this first registration until the server is fully initialized
+        // will be freed in the callback
+        // todo: malloc may fail: return a statuscode
+        struct PeriodicServerRegisterJob *newRetryJob =
+            (struct PeriodicServerRegisterJob *)UA_malloc(sizeof(struct PeriodicServerRegisterJob));
+        newRetryJob->job = (UA_Job*)UA_malloc(sizeof(UA_Job));
+        newRetryJob->this_interval = 1;
+        newRetryJob->default_interval = intervalMs;
+        newRetryJob->job->type = UA_JOBTYPE_METHODCALL;
+        newRetryJob->job->job.methodCall.method = periodicServerRegister;
+        newRetryJob->job->job.methodCall.data = newRetryJob;
+        newRetryJob->discovery_server_url = discoveryServerUrl;
+        retval = UA_Server_addRepeatedJob(server, *newRetryJob->job,
+                                          delayFirstRegisterMs, &newRetryJob->job_id);
+        if (retval != UA_STATUSCODE_GOOD) {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "Could not create first job for server register. "
+                         "StatusCode %s", UA_StatusCode_name(retval));
+            return retval;
+        }
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+void
+UA_Server_setRegisterServerCallback(UA_Server *server, UA_Server_registerServerCallback cb,
+                                    void* data) {
+    server->registerServerCallback = cb;
+    server->registerServerCallbackData = data;
+}
+
+#endif // UA_ENABLE_DISCOVERY

+ 551 - 0
src/server/ua_services_discovery_multicast.c

@@ -0,0 +1,551 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_server_internal.h"
+#include "ua_services.h"
+#include "ua_mdns_internal.h"
+
+#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST)
+
+#ifdef _MSC_VER
+# ifndef UNDER_CE
+#  include <io.h> //access
+#  define access _access
+# endif
+#else
+# include <unistd.h> //access
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+#ifdef _WIN32
+# define CLOSESOCKET(S) closesocket((SOCKET)S)
+#else
+# define CLOSESOCKET(S) close(S)
+#endif
+
+void Service_FindServersOnNetwork(UA_Server *server, UA_Session *session,
+                                  const UA_FindServersOnNetworkRequest *request,
+                                  UA_FindServersOnNetworkResponse *response) {
+    UA_UInt32 recordCount;
+    if(request->startingRecordId < server->serverOnNetworkRecordIdCounter)
+        recordCount = server->serverOnNetworkRecordIdCounter - request->startingRecordId;
+    else
+        recordCount = 0;
+
+    if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn)
+        recordCount = recordCount > request->maxRecordsToReturn ? request->maxRecordsToReturn : recordCount;
+
+    UA_ServerOnNetwork** filtered = NULL;
+    if(recordCount > 0) {
+        filtered = (UA_ServerOnNetwork**)UA_malloc(sizeof(UA_ServerOnNetwork*) * recordCount);
+        if(!filtered) {
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            return;
+        }
+        memset(filtered, 0, sizeof(UA_ServerOnNetwork*) * recordCount);
+
+        if(request->serverCapabilityFilterSize) {
+            UA_UInt32 filteredCount = 0;
+            // iterate over all records and add to filtered list
+            serverOnNetwork_list_entry* current;
+            LIST_FOREACH(current, &server->serverOnNetwork, pointers) {
+                if (current->serverOnNetwork.recordId < request->startingRecordId)
+                    continue;
+                UA_Boolean foundAll = UA_TRUE;
+                for(size_t i = 0; i < request->serverCapabilityFilterSize && foundAll; i++) {
+                    UA_Boolean foundSingle = UA_FALSE;
+                    for(size_t j = 0; j < current->serverOnNetwork.serverCapabilitiesSize && !foundSingle; j++)
+                        foundSingle |= UA_String_equal(&request->serverCapabilityFilter[i],
+                                                       &current->serverOnNetwork.serverCapabilities[j]);
+                    foundAll &= foundSingle;
+                }
+                if (foundAll) {
+                    filtered[filteredCount++] = &current->serverOnNetwork;
+                    if (filteredCount >= recordCount)
+                        break;
+                }
+            }
+            recordCount = filteredCount;
+        } else {
+            UA_UInt32 filteredCount = 0;
+            serverOnNetwork_list_entry* current;
+            LIST_FOREACH(current, &server->serverOnNetwork, pointers) {
+                if (current->serverOnNetwork.recordId < request->startingRecordId)
+                    continue;
+                filtered[filteredCount++] = &current->serverOnNetwork;
+                if (filteredCount >= recordCount)
+                    break;
+            }
+            recordCount = filteredCount;
+        }
+    }
+
+    response->serversSize = recordCount;
+    if(recordCount > 0) {
+        response->servers = (UA_ServerOnNetwork *)UA_malloc(sizeof(UA_ServerOnNetwork)*recordCount);
+        if(!response->servers) {
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+            free(filtered);
+            return;
+        }
+
+        for(size_t i = 0; i < recordCount; i++)
+            UA_ServerOnNetwork_copy(filtered[i],&response->servers[recordCount-i-1]);
+    }
+    free(filtered);
+
+    UA_DateTime_copy(&server->serverOnNetworkRecordIdLastReset, &response->lastCounterResetTime);
+    response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
+}
+
+void
+UA_Discovery_update_MdnsForDiscoveryUrl(UA_Server *server, const char *serverName,
+                                        UA_MdnsDiscoveryConfiguration *mdnsConfig,
+                                        const UA_String discoveryUrl, UA_Boolean isOnline,
+                                        UA_Boolean updateTxt) {
+    UA_UInt16 port = 0;
+    char hostname[256]; hostname[0] = '\0';
+    const char *path = NULL;
+
+    size_t uriSize = sizeof(char) * discoveryUrl.length + 1;
+
+    // todo: malloc may fail: return a statuscode
+    char* uri = (char*)malloc(uriSize);
+    strncpy(uri, (char*) discoveryUrl.data, discoveryUrl.length);
+    uri[discoveryUrl.length] = '\0';
+
+    UA_StatusCode retval = UA_EndpointUrl_split(uri, hostname, &port, &path);
+    if (retval != UA_STATUSCODE_GOOD) {
+        hostname[0] = '\0';
+        if (retval == UA_STATUSCODE_BADOUTOFRANGE)
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_NETWORK,
+                           "Server url size invalid");
+        else if (retval == UA_STATUSCODE_BADATTRIBUTEIDINVALID)
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_NETWORK,
+                           "Server url does not begin with opc.tcp://");
+    }
+    free(uri);
+
+    if(!isOnline) {
+        UA_StatusCode removeRetval =
+                UA_Discovery_removeRecord(server, serverName, hostname,
+                                          (unsigned short) port, updateTxt);
+        if(removeRetval != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                           "Could not remove mDNS record for hostname %s.", serverName);
+        }
+    } else {
+        UA_String *capabilities = NULL;
+        size_t capabilitiesSize = 0;
+        if(mdnsConfig) {
+            capabilities = mdnsConfig->serverCapabilities;
+            capabilitiesSize = mdnsConfig->serverCapabilitiesSize;
+        }
+        UA_StatusCode addRetval =
+                UA_Discovery_addRecord(server, serverName, hostname,
+                                       (unsigned short) port, path,
+                                       UA_DISCOVERY_TCP, updateTxt, capabilities, &capabilitiesSize);
+        if(addRetval != UA_STATUSCODE_GOOD) {
+            UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                           "Could not add mDNS record for hostname %s.", serverName);
+        }
+    }
+}
+
+void
+UA_Server_setServerOnNetworkCallback(UA_Server *server, UA_Server_serverOnNetworkCallback cb,
+                                     void* data) {
+    server->serverOnNetworkCallback = cb;
+    server->serverOnNetworkCallbackData = data;
+}
+
+static void
+socket_mdns_set_nonblocking(int sockfd) {
+#ifdef _WIN32
+    u_long iMode = 1;
+    ioctlsocket(sockfd, FIONBIO, &iMode);
+#else
+    int opts = fcntl(sockfd, F_GETFL);
+    fcntl(sockfd, F_SETFL, opts|O_NONBLOCK);
+#endif
+}
+
+/* Create multicast 224.0.0.251:5353 socket */
+static int discovery_createMulticastSocket(void) {
+    int s, flag = 1, ittl = 255;
+    struct sockaddr_in in;
+    struct ip_mreq mc;
+    char ttl = (char)255; // publish to complete net, not only subnet. See:
+                          // https://docs.oracle.com/cd/E23824_01/html/821-1602/sockets-137.html
+
+    memset(&in, 0, sizeof(in));
+    in.sin_family = AF_INET;
+    in.sin_port = htons(5353);
+    in.sin_addr.s_addr = 0;
+
+    if ((s = (int)socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+        return 0;
+
+#ifdef SO_REUSEPORT
+    setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (char *)&flag, sizeof(flag));
+#endif
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag));
+    if (bind(s, (struct sockaddr *)&in, sizeof(in))) {
+        CLOSESOCKET(s);
+        return 0;
+    }
+
+    mc.imr_multiaddr.s_addr = inet_addr("224.0.0.251");
+    mc.imr_interface.s_addr = htonl(INADDR_ANY);
+    setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mc, sizeof(mc));
+    setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ttl, sizeof(ttl));
+    setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl, sizeof(ittl));
+
+    socket_mdns_set_nonblocking(s);
+    return s;
+}
+
+UA_StatusCode
+UA_Discovery_multicastInit(UA_Server* server) {
+    server->mdnsDaemon = mdnsd_new(QCLASS_IN, 1000);
+    if((server->mdnsSocket = discovery_createMulticastSocket()) == 0) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Could not create multicast socket. Error: %s", strerror(errno));
+        return UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
+    mdnsd_register_receive_callback(server->mdnsDaemon, mdns_record_received, server);
+    return UA_STATUSCODE_GOOD;
+}
+
+void UA_Discovery_multicastDestroy(UA_Server* server) {
+    mdnsd_shutdown(server->mdnsDaemon);
+    mdnsd_free(server->mdnsDaemon);
+}
+
+static void
+UA_Discovery_multicastConflict(char *name, int type, void *arg) {
+    // cppcheck-suppress unreadVariable
+    UA_Server *server = (UA_Server*) arg;
+    UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                 "Multicast DNS name conflict detected: '%s' for type %d", name, type);
+}
+
+static char*
+create_fullServiceDomain(const char* servername, const char* hostname, size_t maxLen) {
+    size_t hostnameLen = strlen(hostname);
+    size_t servernameLen = strlen(servername);
+    // [servername]-[hostname]._opcua-tcp._tcp.local.
+
+    if(hostnameLen+servernameLen+1 > maxLen) {
+        if (servernameLen+2 > maxLen) {
+            servernameLen = maxLen;
+            hostnameLen = 0;
+        } else {
+            hostnameLen = maxLen - servernameLen - 1;
+        }
+    }
+
+    char *fullServiceDomain = (char *)malloc(servernameLen + 1 + hostnameLen + 23 + 2);
+    if (!fullServiceDomain)
+        return NULL;
+
+    if (hostnameLen > 0)
+        sprintf(fullServiceDomain, "%.*s-%.*s._opcua-tcp._tcp.local.",
+                (int)servernameLen, servername, (int)hostnameLen, hostname);
+    else
+        sprintf(fullServiceDomain, "%.*s._opcua-tcp._tcp.local.",
+                (int)servernameLen, servername);
+    return fullServiceDomain;
+}
+
+/* Check if mDNS already has an entry for given hostname and port combination */
+static UA_Boolean
+UA_Discovery_recordExists(UA_Server* server, const char* fullServiceDomain,
+                          unsigned short port, const UA_DiscoveryProtocol protocol) {
+    // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].
+    mdns_record_t *r  = mdnsd_get_published(server->mdnsDaemon, fullServiceDomain);
+    while (r) {
+        const mdns_answer_t *data = mdnsd_record_data(r);
+        if (data->type == QTYPE_SRV && (port == 0 || data->srv.port == port))
+            return UA_TRUE;
+        r = mdnsd_record_next(r);
+    }
+    return UA_FALSE;
+}
+
+static int
+discovery_multicastQueryAnswer(mdns_answer_t *a, void *arg) {
+    UA_Server *server = (UA_Server*) arg;
+    if(a->type != QTYPE_PTR)
+        return 0;
+
+    if(a->rdname == NULL)
+        return 0;
+
+    /* Skip, if we already know about this server */
+    UA_Boolean exists =
+        UA_Discovery_recordExists(server, a->rdname, 0, UA_DISCOVERY_TCP);
+    if(exists == UA_TRUE)
+        return 0;
+
+    if(mdnsd_has_query(server->mdnsDaemon, a->rdname))
+        return 0;
+
+    UA_LOG_DEBUG(server->config.logger, UA_LOGCATEGORY_SERVER,
+                 "mDNS send query for: %s SRV&TXT %s", a->name, a->rdname);
+
+    mdnsd_query(server->mdnsDaemon, a->rdname, QTYPE_SRV,
+                discovery_multicastQueryAnswer, server);
+    mdnsd_query(server->mdnsDaemon, a->rdname, QTYPE_TXT,
+                discovery_multicastQueryAnswer, server);
+    return 0;
+}
+
+/* Send a multicast probe to find any other OPC UA server on the network through mDNS. */
+UA_StatusCode
+UA_Discovery_multicastQuery(UA_Server* server) {
+    mdnsd_query(server->mdnsDaemon, "_opcua-tcp._tcp.local.",
+                QTYPE_PTR,discovery_multicastQueryAnswer, server);
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode
+UA_Discovery_addRecord(UA_Server* server, const char* servername,
+                       const char* hostname, unsigned short port,
+                       const char* path, const UA_DiscoveryProtocol protocol,
+                       UA_Boolean createTxt, const UA_String* capabilites,
+                       const size_t *capabilitiesSize) {
+    if(!capabilitiesSize || (*capabilitiesSize > 0 && !capabilites))
+        return UA_STATUSCODE_BADINVALIDARGUMENT;
+
+    size_t hostnameLen = strlen(hostname);
+    size_t servernameLen = strlen(servername);
+    if(hostnameLen == 0 || servernameLen == 0)
+        return UA_STATUSCODE_BADOUTOFRANGE;
+
+    // use a limit for the hostname length to make sure full string fits into 63
+    // chars (limited by DNS spec)
+    if(hostnameLen+servernameLen + 1 > 63) { // include dash between servername-hostname
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                       "Multicast DNS: Combination of hostname+servername exceeds maximum "
+                       "of 62 chars. It will be truncated.");
+    } else if(hostnameLen > 63) {
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                       "Multicast DNS: Hostname length exceeds maximum of 63 chars. "
+                       "It will be truncated.");
+    }
+
+    if(!server->mdnsMainSrvAdded) {
+        mdns_record_t *r = mdnsd_shared(server->mdnsDaemon, "_services._dns-sd._udp.local.",
+                                        QTYPE_PTR, 600);
+        mdnsd_set_host(server->mdnsDaemon, r, "_opcua-tcp._tcp.local.");
+        server->mdnsMainSrvAdded = UA_TRUE;
+    }
+
+    // [servername]-[hostname]._opcua-tcp._tcp.local.
+    char *fullServiceDomain = create_fullServiceDomain(servername, hostname, 63);
+    if(!fullServiceDomain)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    UA_Boolean exists = UA_Discovery_recordExists(server, fullServiceDomain, port, protocol);
+    if(exists == UA_TRUE) {
+        free(fullServiceDomain);
+        return UA_STATUSCODE_GOOD;
+    }
+
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                "Multicast DNS: add record for domain: %s", fullServiceDomain);
+
+    // _services._dns-sd._udp.local. PTR _opcua-tcp._tcp.local
+
+    // check if there is already a PTR entry for the given service.
+
+    // _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local.
+    mdns_record_t *r = mdns_find_record(server->mdnsDaemon, QTYPE_PTR,
+                                        "_opcua-tcp._tcp.local.", fullServiceDomain);
+    if(!r) {
+        r = mdnsd_shared(server->mdnsDaemon, "_opcua-tcp._tcp.local.", QTYPE_PTR, 600);
+        mdnsd_set_host(server->mdnsDaemon, r, fullServiceDomain);
+    }
+
+    // hostname.
+    size_t maxHostnameLen = hostnameLen < 63 ? hostnameLen : 63;
+    char *localDomain = (char *)malloc(maxHostnameLen+2);
+    if(!localDomain) {
+        free(fullServiceDomain);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    sprintf(localDomain, "%.*s.",(int)(maxHostnameLen), hostname);
+
+    // [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port [hostname].
+    r = mdnsd_unique(server->mdnsDaemon, fullServiceDomain, QTYPE_SRV, 600,
+                     UA_Discovery_multicastConflict, server);
+    // r = mdnsd_shared(server->mdnsDaemon, fullServiceDomain, QTYPE_SRV, 600);
+    mdnsd_set_srv(server->mdnsDaemon, r, 0, 0, port, localDomain);
+
+    // A/AAAA record for all ip addresses.
+    // [servername]-[hostname]._opcua-tcp._tcp.local. A [ip].
+    // [hostname]. A [ip].
+    mdns_set_address_record(server, fullServiceDomain, localDomain);
+
+    // TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,...
+    if(createTxt) {
+        mdns_create_txt(server, fullServiceDomain, path, capabilites, capabilitiesSize,
+                        UA_Discovery_multicastConflict);
+    }
+
+    free(fullServiceDomain);
+    free(localDomain);
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode
+UA_Discovery_removeRecord(UA_Server* server, const char* servername, const char* hostname,
+                          unsigned short port, UA_Boolean removeTxt) {
+    size_t hostnameLen = strlen(hostname);
+    size_t servernameLen = strlen(servername);
+    // use a limit for the hostname length to make sure full string fits into 63
+    // chars (limited by DNS spec)
+    if(hostnameLen == 0 || servernameLen == 0)
+        return UA_STATUSCODE_BADOUTOFRANGE;
+
+    if(hostnameLen+servernameLen+1 > 63) { // include dash between servername-hostname
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                       "Multicast DNS: Combination of hostname+servername exceeds "
+                       "maximum of 62 chars. It will be truncated.");
+    }
+
+    // [servername]-[hostname]._opcua-tcp._tcp.local.
+    char *fullServiceDomain = create_fullServiceDomain(servername, hostname, 63);
+    if(!fullServiceDomain)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    UA_LOG_INFO(server->config.logger, UA_LOGCATEGORY_SERVER,
+                "Multicast DNS: remove record for domain: %s", fullServiceDomain);
+
+    // _opcua-tcp._tcp.local. PTR [servername]-[hostname]._opcua-tcp._tcp.local.
+    mdns_record_t *r = mdns_find_record(server->mdnsDaemon, QTYPE_PTR,
+                                        "_opcua-tcp._tcp.local.", fullServiceDomain);
+    if(!r) {
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                       "Multicast DNS: could not remove record. "
+                       "PTR Record not found for domain: %s", fullServiceDomain);
+        free(fullServiceDomain);
+        return UA_STATUSCODE_BADNOTFOUND;
+    }
+    mdnsd_done(server->mdnsDaemon, r);
+
+    // looks for [servername]-[hostname]._opcua-tcp._tcp.local. 86400 IN SRV 0 5 port hostname.local.
+    // and TXT record: [servername]-[hostname]._opcua-tcp._tcp.local. TXT path=/ caps=NA,DA,...
+    // and A record: [servername]-[hostname]._opcua-tcp._tcp.local. A [ip]
+    mdns_record_t *r2 = mdnsd_get_published(server->mdnsDaemon, fullServiceDomain);
+    if(!r2) {
+        UA_LOG_WARNING(server->config.logger, UA_LOGCATEGORY_SERVER,
+                       "Multicast DNS: could not remove record. Record not "
+                       "found for domain: %s", fullServiceDomain);
+        free(fullServiceDomain);
+        return UA_STATUSCODE_BADNOTFOUND;
+    }
+
+    while(r2) {
+        const mdns_answer_t *data = mdnsd_record_data(r2);
+        mdns_record_t *next = mdnsd_record_next(r2);
+        if((removeTxt && data->type == QTYPE_TXT) ||
+           (removeTxt && data->type == QTYPE_A) ||
+           data->srv.port == port) {
+            mdnsd_done(server->mdnsDaemon, r2);
+        }
+        r2 = next;
+    }
+
+    free(fullServiceDomain);
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode
+UA_Discovery_multicastIterate(UA_Server* server, UA_DateTime *nextRepeat,
+                              UA_Boolean processIn) {
+    struct timeval next_sleep = { 0, 0 };
+    unsigned short retVal = mdnsd_step(server->mdnsDaemon, server->mdnsSocket,
+                                       processIn, true, &next_sleep);
+    if(retVal == 1) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Multicast error: Can not read from socket. %s",
+                     strerror(errno));
+        return UA_STATUSCODE_BADNOCOMMUNICATION;
+    } else if(retVal == 2) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Multicast error: Can not write to socket. %s",
+                     strerror(errno));
+        return UA_STATUSCODE_BADNOCOMMUNICATION;
+    }
+
+    if(nextRepeat)
+        *nextRepeat = UA_DateTime_now() +
+            (UA_DateTime)(next_sleep.tv_sec * UA_SEC_TO_DATETIME +
+                          next_sleep.tv_usec * UA_USEC_TO_DATETIME);
+    return UA_STATUSCODE_GOOD;
+}
+
+#ifdef UA_ENABLE_MULTITHREADING
+
+static void *
+multicastWorkerLoop(UA_Server *server) {
+    struct timeval next_sleep = {.tv_sec = 0, .tv_usec = 0};
+    volatile UA_Boolean *running = &server->mdnsRunning;
+    fd_set fds;
+
+    while(*running) {
+        FD_ZERO(&fds);
+        FD_SET(server->mdnsSocket, &fds);
+        select(server->mdnsSocket + 1, &fds, 0, 0, &next_sleep);
+
+        if(!*running)
+            break;
+
+        unsigned short retVal =
+            mdnsd_step(server->mdnsDaemon, server->mdnsSocket,
+                       FD_ISSET(server->mdnsSocket, &fds), true, &next_sleep);
+        if (retVal == 1) {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "Multicast error: Can not read from socket. %s", strerror(errno));
+            break;
+        } else if (retVal == 2) {
+            UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                         "Multicast error: Can not write to socket. %s", strerror(errno));
+            break;
+        }
+    }
+    return NULL;
+}
+
+UA_StatusCode
+UA_Discovery_multicastListenStart(UA_Server* server) {
+    int err = pthread_create(&server->mdnsThread, NULL,
+                             (void* (*)(void*))multicastWorkerLoop, server);
+    if(err != 0) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Multicast error: Can not create multicast thread.");
+        return UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode
+UA_Discovery_multicastListenStop(UA_Server* server) {
+    mdnsd_shutdown(server->mdnsDaemon);
+    // wake up select
+    write(server->mdnsSocket, "\0", 1);
+    if(pthread_join(server->mdnsThread, NULL)) {
+        UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_SERVER,
+                     "Multicast error: Can not stop thread.");
+        return UA_STATUSCODE_BADUNEXPECTEDERROR;
+    }
+    return UA_STATUSCODE_BADNOTIMPLEMENTED;
+}
+
+# endif /* UA_ENABLE_MULTITHREADING */
+
+#endif /* defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) */

+ 4 - 1
src/ua_securechannel.c

@@ -374,7 +374,10 @@ UA_SecureChannel_processChunks(UA_SecureChannel *channel, const UA_ByteString *c
             if(retval != UA_STATUSCODE_GOOD)
                 break;
 
-            callback(application, channel, UA_MESSAGETYPE_ERR, 0, (void*)&errorMessage);
+            // dirty cast to pass errorMessage
+            UA_UInt32 val = 0;
+            callback(application, (UA_SecureChannel *)channel, (UA_MessageType)UA_MESSAGETYPE_ERR,
+                     val, (const UA_ByteString*)&errorMessage);
             continue;
         }
 

+ 343 - 62
tests/check_discovery.c

@@ -1,14 +1,29 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public 
-*  License, v. 2.0. If a copy of the MPL was not distributed with this 
+/* This Source Code Form is subject to the terms of the Mozilla Public
+*  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+
+// On older systems we need to define _BSD_SOURCE
+// _DEFAULT_SOURCE is an alias for that
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
 #include <pthread.h>
 #include <ua_util.h>
 #include <ua_types_generated.h>
 #include <server/ua_server_internal.h>
-#include <unistd.h>
+#include <ua_types.h>
+#include <fcntl.h>
 
 #include "ua_client.h"
 #include "ua_config_standard.h"
@@ -38,7 +53,14 @@ static void setup_lds(void) {
     *running_lds = true;
     UA_ServerConfig config_lds = UA_ServerConfig_standard;
     config_lds.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
-    config_lds.applicationDescription.applicationUri = UA_String_fromChars("open62541.test.local_discovery_server");
+    config_lds.applicationDescription.applicationUri = UA_String_fromChars("urn:open62541.test.local_discovery_server");
+    config_lds.applicationDescription.applicationName.locale = UA_String_fromChars("en");
+    config_lds.applicationDescription.applicationName.text = UA_String_fromChars("LDS Server");
+    config_lds.mdnsServerName = UA_String_fromChars("LDS_test");
+    config_lds.serverCapabilitiesSize = 1;
+    UA_String *caps = UA_String_new();
+    *caps = UA_String_fromChars("LDS");
+    config_lds.serverCapabilities = caps;
     config_lds.discoveryCleanupTimeout = registerTimeout;
     nl_lds = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 4840);
     config_lds.networkLayers = &nl_lds;
@@ -46,6 +68,8 @@ static void setup_lds(void) {
     server_lds = UA_Server_new(config_lds);
     UA_Server_run_startup(server_lds);
     pthread_create(&server_thread_lds, NULL, serverloop_lds, NULL);
+    // wait until LDS started
+    sleep(1);
 }
 
 static void teardown_lds(void) {
@@ -54,6 +78,9 @@ static void teardown_lds(void) {
     UA_Server_run_shutdown(server_lds);
     UA_Boolean_delete(running_lds);
     UA_String_deleteMembers(&server_lds->config.applicationDescription.applicationUri);
+    UA_LocalizedText_deleteMembers(&server_lds->config.applicationDescription.applicationName);
+    UA_String_deleteMembers(&server_lds->config.mdnsServerName);
+    UA_Array_delete(server_lds->config.serverCapabilities, server_lds->config.serverCapabilitiesSize, &UA_TYPES[UA_TYPES_STRING]);
     UA_Server_delete(server_lds);
     nl_lds.deleteMembers(&nl_lds);
 }
@@ -64,6 +91,8 @@ UA_Boolean *running_register;
 UA_ServerNetworkLayer nl_register;
 pthread_t server_thread_register;
 
+UA_Guid periodicRegisterJobId;
+
 static void * serverloop_register(void *_) {
     while(*running_register)
         UA_Server_run_iterate(server_register, true);
@@ -71,11 +100,14 @@ static void * serverloop_register(void *_) {
 }
 
 static void setup_register(void) {
-    // start LDS server
+    // start register server
     running_register = UA_Boolean_new();
     *running_register = true;
     UA_ServerConfig config_register = UA_ServerConfig_standard;
-    config_register.applicationDescription.applicationUri = UA_String_fromChars("open62541.test.server_register");
+    config_register.applicationDescription.applicationUri = UA_String_fromChars("urn:open62541.test.server_register");
+    config_register.applicationDescription.applicationName.locale = UA_String_fromChars("de");
+    config_register.applicationDescription.applicationName.text = UA_String_fromChars("Anmeldungsserver");
+    config_register.mdnsServerName = UA_String_fromChars("Register_test");
     nl_register = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
     config_register.networkLayers = &nl_register;
     config_register.networkLayersSize = 1;
@@ -90,10 +122,20 @@ static void teardown_register(void) {
     UA_Server_run_shutdown(server_register);
     UA_Boolean_delete(running_register);
     UA_String_deleteMembers(&server_register->config.applicationDescription.applicationUri);
+    UA_LocalizedText_deleteMembers(&server_register->config.applicationDescription.applicationName);
+    UA_String_deleteMembers(&server_register->config.mdnsServerName);
     UA_Server_delete(server_register);
     nl_register.deleteMembers(&nl_register);
 }
 
+
+static char* UA_String_to_char_alloc(const UA_String *str) {
+    char* ret = malloc(sizeof(char)*str->length+1);
+    memcpy( ret, str->data, str->length );
+    ret[str->length] = '\0';
+    return ret;
+}
+
 START_TEST(Server_register) {
         UA_StatusCode retval = UA_Server_register_discovery(server_register, "opc.tcp://localhost:4840", NULL);
         ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
@@ -107,118 +149,313 @@ START_TEST(Server_unregister) {
 END_TEST
 
 
-static UA_StatusCode FindServers(const char* discoveryServerUrl, size_t* registeredServerSize, UA_ApplicationDescription** registeredServers, const char* filterUri, const char* filterLocale) {
-    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;
+START_TEST(Server_register_semaphore) {
+        // create the semaphore
+        int fd = open("/tmp/open62541-unit-test-semaphore", O_RDWR|O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
+        ck_assert_int_ne(fd, -1);
+        close(fd);
+
+        UA_StatusCode retval = UA_Server_register_discovery(server_register, "opc.tcp://localhost:4840", "/tmp/open62541-unit-test-semaphore");
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
     }
+END_TEST
 
+START_TEST(Server_unregister_semaphore) {
+        // delete the semaphore, this should remove the registration automatically on next check
+        ck_assert_int_eq(remove("/tmp/open62541-unit-test-semaphore"), 0);
+    }
+END_TEST
 
-    UA_FindServersRequest request;
-    UA_FindServersRequest_init(&request);
+START_TEST(Server_register_periodic) {
+        // periodic register every minute, first register immediately
+        UA_StatusCode retval = UA_Server_addPeriodicServerRegisterJob(server_register, NULL, 60*1000, 100, &periodicRegisterJobId);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    }
+END_TEST
+
+START_TEST(Server_unregister_periodic) {
+        // wait for first register delay
+        sleep(1);
+        UA_Server_removeRepeatedJob(server_register, periodicRegisterJobId);
+        UA_StatusCode retval = UA_Server_unregister_discovery(server_register, NULL);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    }
+END_TEST
+
+static void FindAndCheck(const char* expectedUris[], size_t expectedUrisSize, const char* expectedLocales[], const char* expectedNames[], const char *filterUri, const char *filterLocale) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+
+    UA_ApplicationDescription* applicationDescriptionArray = NULL;
+    size_t applicationDescriptionArraySize = 0;
+
+    size_t serverUrisSize = 0;
+    UA_String *serverUris = NULL;
 
     if (filterUri) {
-        request.serverUrisSize = 1;
-        request.serverUris = UA_malloc(sizeof(UA_String));
-        request.serverUris[0] = UA_String_fromChars(filterUri);
+        serverUrisSize = 1;
+        serverUris = UA_malloc(sizeof(UA_String));
+        serverUris[0] = UA_String_fromChars(filterUri);
     }
 
+    size_t localeIdsSize = 0;
+    UA_String *localeIds = NULL;
+
     if (filterLocale) {
-        request.localeIdsSize = 1;
-        request.localeIds = UA_malloc(sizeof(UA_String));
-        request.localeIds[0] = UA_String_fromChars(filterLocale);
+        localeIdsSize = 1;
+        localeIds = UA_malloc(sizeof(UA_String));
+        localeIds[0] = UA_String_fromChars(filterLocale);
     }
 
-    // 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_StatusCode retval = UA_Client_findServers(client, "opc.tcp://localhost:4840",
+                                                 serverUrisSize, serverUris, localeIdsSize, localeIds,
+                                                 &applicationDescriptionArraySize, &applicationDescriptionArray);
 
     if (filterUri) {
-        UA_Array_delete(request.serverUris, request.serverUrisSize, &UA_TYPES[UA_TYPES_STRING]);
+        UA_Array_delete(serverUris, serverUrisSize, &UA_TYPES[UA_TYPES_STRING]);
     }
 
     if (filterLocale) {
-        UA_Array_delete(request.localeIds, request.localeIdsSize, &UA_TYPES[UA_TYPES_STRING]);
+        UA_Array_delete(localeIds, localeIdsSize, &UA_TYPES[UA_TYPES_STRING]);
     }
 
-    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-        UA_FindServersResponse_deleteMembers(&response);
-        UA_Client_disconnect(client);
-        UA_Client_delete(client);
-        ck_abort_msg("FindServers failed with statuscode 0x%08x", response.responseHeader.serviceResult);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    // only the discovery server is expected
+    ck_assert_uint_eq(applicationDescriptionArraySize, expectedUrisSize);
+    assert(applicationDescriptionArray != NULL);
+
+    for (size_t i=0; i < expectedUrisSize; ++i) {
+        char* serverUri = UA_String_to_char_alloc(&applicationDescriptionArray[i].applicationUri);
+        ck_assert_str_eq(serverUri, expectedUris[i]);
+        free(serverUri);
+
+        if (expectedNames && expectedNames[i] != NULL) {
+            char *name = UA_String_to_char_alloc(&applicationDescriptionArray[i].applicationName.text);
+            ck_assert_str_eq(name, expectedNames[i]);
+            free(name);
+        }
+        if (expectedLocales && expectedLocales[i] != NULL) {
+            char *locale = UA_String_to_char_alloc(&applicationDescriptionArray[i].applicationName.locale);
+            ck_assert_str_eq(locale, expectedLocales[i]);
+            free(locale);
+        }
     }
 
-    *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_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
 
     UA_Client_disconnect(client);
     UA_Client_delete(client);
-    return (int) UA_STATUSCODE_GOOD;
 }
 
-static void FindAndCheck(const char* expectedUris[], size_t expectedUrisSize, const char *filterUri, const char *filterLocale) {
+static void FindOnNetworkAndCheck(char* expectedServerNames[], size_t expectedServerNamesSize, const char *filterUri, const char *filterLocale,
+                                  const char** filterCapabilities, size_t filterCapabilitiesSize) {
     UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
-    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
 
-    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_ServerOnNetwork* serverOnNetwork = NULL;
+    size_t serverOnNetworkSize = 0;
 
-    UA_ApplicationDescription* applicationDescriptionArray = NULL;
-    size_t applicationDescriptionArraySize = 0;
 
-    retval = FindServers("opc.tcp://localhost:4840", &applicationDescriptionArraySize, &applicationDescriptionArray, filterUri, filterLocale);
+    size_t  serverCapabilityFilterSize = 0;
+    UA_String *serverCapabilityFilter = NULL;
+
+    if (filterCapabilitiesSize) {
+        serverCapabilityFilterSize = filterCapabilitiesSize;
+        serverCapabilityFilter = UA_malloc(sizeof(UA_String) * filterCapabilitiesSize);
+        for (size_t i=0; i<filterCapabilitiesSize; i++) {
+            serverCapabilityFilter[i] = UA_String_fromChars(filterCapabilities[i]);
+        }
+    }
+
+
+    UA_StatusCode retval = UA_Client_findServersOnNetwork(client, "opc.tcp://localhost:4840", 0, 0,
+                                                          serverCapabilityFilterSize, serverCapabilityFilter,
+                                                          &serverOnNetworkSize, &serverOnNetwork);
+
+    if (serverCapabilityFilterSize) {
+        UA_Array_delete(serverCapabilityFilter, serverCapabilityFilterSize, &UA_TYPES[UA_TYPES_STRING]);
+    }
+
     ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
 
     // only the discovery server is expected
-    ck_assert_uint_eq(applicationDescriptionArraySize, expectedUrisSize);
-    assert(applicationDescriptionArray != NULL);
+    ck_assert_uint_eq(serverOnNetworkSize , expectedServerNamesSize);
 
-    for(size_t i=0; i < expectedUrisSize; ++i) {
-        char* serverUri = malloc(sizeof(char)*applicationDescriptionArray[i].applicationUri.length+1);
-        memcpy(serverUri, applicationDescriptionArray[i].applicationUri.data, applicationDescriptionArray[i].applicationUri.length);
-        serverUri[applicationDescriptionArray[i].applicationUri.length] = '\0';
-        ck_assert_str_eq(serverUri, expectedUris[i]);
-        free(serverUri);
+    if (expectedServerNamesSize > 0) {
+        ck_assert_ptr_ne(serverOnNetwork, NULL);
     }
 
-    UA_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
+    if (serverOnNetwork != NULL) {
+        for (size_t i=0; i<expectedServerNamesSize; i++) {
+            char* serverName = malloc(sizeof(char) * (serverOnNetwork[i].serverName.length+1));
+            memcpy( serverName, serverOnNetwork[i].serverName.data, serverOnNetwork[i].serverName.length );
+            serverName[serverOnNetwork[i].serverName.length] = '\0';
+            ck_assert_str_eq(serverName, expectedServerNames[i]);
+            free(serverName);
+        }
+    }
+
+    UA_Array_delete(serverOnNetwork, serverOnNetworkSize, &UA_TYPES[UA_TYPES_SERVERONNETWORK]);
 
     UA_Client_disconnect(client);
     UA_Client_delete(client);
 }
 
+static UA_StatusCode GetEndpoints(UA_Client *client, const UA_String* endpointUrl, size_t* endpointDescriptionsSize, UA_EndpointDescription** endpointDescriptions,
+                                  const char* filterTransportProfileUri
+) {
+    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
+    if (filterTransportProfileUri) {
+        request.profileUrisSize = 1;
+        request.profileUris = UA_malloc(sizeof(UA_String));
+        request.profileUris[0] = UA_String_fromChars(filterTransportProfileUri);
+    }
+
+    UA_GetEndpointsResponse response;
+    UA_GetEndpointsResponse_init(&response);
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE]);
+
+    if (filterTransportProfileUri) {
+        UA_Array_delete(request.profileUris, request.profileUrisSize, &UA_TYPES[UA_TYPES_STRING]);
+    }
+
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+
+    *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;
+}
+
+
+static void GetEndpointsAndCheck(const char* discoveryUrl, const char* filterTransportProfileUri, const char* expectedEndpointUrls[], size_t expectedEndpointUrlsSize) {
+
+    UA_Client *client = UA_Client_new(UA_ClientConfig_standard);
+
+    ck_assert_uint_eq(UA_Client_connect(client, discoveryUrl), UA_STATUSCODE_GOOD);
+
+    UA_EndpointDescription* endpointArray = NULL;
+    size_t endpointArraySize = 0;
+    UA_String discoveryUrlUA = UA_String_fromChars(discoveryUrl);
+    UA_StatusCode retval = GetEndpoints(client, &discoveryUrlUA, &endpointArraySize, &endpointArray, filterTransportProfileUri);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_String_deleteMembers(&discoveryUrlUA);
+
+    ck_assert_uint_eq(endpointArraySize , expectedEndpointUrlsSize);
+
+    for(size_t j = 0; j < endpointArraySize && j < expectedEndpointUrlsSize; j++) {
+        UA_EndpointDescription* endpoint = &endpointArray[j];
+        char *eu = UA_String_to_char_alloc(&endpoint->endpointUrl);
+        ck_assert_ptr_ne(eu, NULL); // clang static analysis fix
+        ck_assert_str_eq(eu, expectedEndpointUrls[j]);
+        free(eu);
+    }
+
+    UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
+    UA_Client_delete(client);
+}
+
 // Test if discovery server lists himself as registered server, before any other registration.
 START_TEST(Client_find_discovery) {
-        const char* expectedUris[] ={"open62541.test.local_discovery_server"};
-        FindAndCheck(expectedUris, 1, NULL, NULL);
+        const char* expectedUris[] ={"urn:open62541.test.local_discovery_server"};
+        FindAndCheck(expectedUris, 1,NULL, NULL, NULL, NULL);
     }
 END_TEST
 
 // Test if discovery server lists himself as registered server if it is filtered by his uri
 START_TEST(Client_filter_discovery) {
-        const char* expectedUris[] ={"open62541.test.local_discovery_server"};
-        FindAndCheck(expectedUris, 1, "open62541.test.local_discovery_server", "en");
+        const char* expectedUris[] ={"urn:open62541.test.local_discovery_server"};
+        FindAndCheck(expectedUris, 1,NULL, NULL, "urn:open62541.test.local_discovery_server", NULL);
+    }
+END_TEST
+
+// Test if server filters locale
+START_TEST(Client_filter_locale) {
+        const char* expectedUris[] ={"urn:open62541.test.local_discovery_server", "urn:open62541.test.server_register"};
+        const char* expectedNames[] ={"LDS Server", "Anmeldungsserver"};
+        const char* expectedLocales[] ={"en", "de"};
+        // even if we request en_US, the server will return de_DE because it only has that name.
+        FindAndCheck(expectedUris, 2,expectedLocales, expectedNames, NULL, "en");
+
     }
 END_TEST
 
 // Test if registered server is returned from LDS
 START_TEST(Client_find_registered) {
+        const char* expectedUris[] ={"urn:open62541.test.local_discovery_server", "urn:open62541.test.server_register"};
+        FindAndCheck(expectedUris, 2, NULL, NULL, NULL, NULL);
+    }
+END_TEST
+
+// Test if registered server is returned from LDS using FindServersOnNetwork
+START_TEST(Client_find_on_network_registered) {
+        char *expectedUris[2];
+        char hostname[256];
+
+        ck_assert_uint_eq(gethostname(hostname, 255), 0);
+
+        //DNS limits name to max 63 chars (+ \0)
+        expectedUris[0] = malloc(64);
+        snprintf(expectedUris[0], 64, "LDS_test-%s", hostname);
+        expectedUris[1] = malloc(64);
+        snprintf(expectedUris[1], 64, "Register_test-%s", hostname);
+        FindOnNetworkAndCheck(expectedUris, 2, NULL, NULL, NULL, 0);
+
+
+        // filter by Capabilities
+        const char* capsLDS[] ={"LDS"};
+        const char* capsNA[] ={"NA"};
+        const char* capsMultiple[] ={"LDS", "NA"};
+
+        // only LDS expected
+        FindOnNetworkAndCheck(expectedUris, 1, NULL, NULL, capsLDS, 1);
+        // only register server expected
+        FindOnNetworkAndCheck(&expectedUris[1], 1, NULL, NULL, capsNA, 1);
+        // no server expected
+        FindOnNetworkAndCheck(NULL, 0, NULL, NULL, capsMultiple, 2);
+
+        free(expectedUris[0]);
+        free(expectedUris[1]);
 
-        const char* expectedUris[] ={"open62541.test.local_discovery_server", "open62541.test.server_register"};
-        FindAndCheck(expectedUris, 2, NULL, NULL);
     }
 END_TEST
 
 // Test if filtering with uris works
 START_TEST(Client_find_filter) {
-        const char* expectedUris[] ={"open62541.test.server_register"};
-        FindAndCheck(expectedUris, 1, "open62541.test.server_register", NULL);
+        const char* expectedUris[] ={"urn:open62541.test.server_register"};
+        FindAndCheck(expectedUris, 1,NULL, NULL, "urn:open62541.test.server_register", NULL);
+    }
+END_TEST
+
+
+START_TEST(Client_get_endpoints) {
+        const char* expectedEndpoints[] ={"opc.tcp://localhost:4840"};
+        // general check if expected endpoints are returned
+        GetEndpointsAndCheck("opc.tcp://localhost:4840", NULL,expectedEndpoints, 1);
+        // check if filtering transport profile still returns the endpoint
+        GetEndpointsAndCheck("opc.tcp://localhost:4840", "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary", expectedEndpoints, 1);
+        // filter transport profily by HTTPS, which should return no endpoint
+        GetEndpointsAndCheck("opc.tcp://localhost:4840", "http://opcfoundation.org/UA-Profile/Transport/https-uabinary", NULL, 0);
+    }
+END_TEST
+
+START_TEST(Util_start_lds) {
+        setup_lds();
+    }
+END_TEST
+
+START_TEST(Util_stop_lds) {
+        teardown_lds();
     }
 END_TEST
 
@@ -228,24 +465,62 @@ START_TEST(Util_wait_timeout) {
     }
 END_TEST
 
+START_TEST(Util_wait_mdns) {
+        sleep(1);
+    }
+END_TEST
+
+START_TEST(Util_wait_startup) {
+        sleep(1);
+    }
+END_TEST
+
+START_TEST(Util_wait_retry) {
+        // first retry is after 2 seconds, then 4, so it should be enough to wait 3 seconds
+        sleep(3);
+    }
+END_TEST
+
 static Suite* testSuite_Client(void) {
     Suite *s = suite_create("Register Server and Client");
     TCase *tc_register = tcase_create("RegisterServer");
     tcase_add_unchecked_fixture(tc_register, setup_lds, teardown_lds);
     tcase_add_unchecked_fixture(tc_register, setup_register, teardown_register);
     tcase_add_test(tc_register, Server_register);
-    // register two times
+    // register two times, just for fun
     tcase_add_test(tc_register, Server_register);
     tcase_add_test(tc_register, Server_unregister);
+    tcase_add_test(tc_register, Server_register_periodic);
+    tcase_add_test(tc_register, Server_unregister_periodic);
     suite_add_tcase(s,tc_register);
 
+    TCase *tc_register_retry = tcase_create("RegisterServer Retry");
+    //tcase_add_unchecked_fixture(tc_register, setup_lds, teardown_lds);
+    tcase_add_unchecked_fixture(tc_register_retry, setup_register, teardown_register);
+    tcase_add_test(tc_register_retry, Server_register_periodic);
+    tcase_add_test(tc_register_retry, Util_wait_startup); // wait a bit to let first try run through
+    // now start LDS
+    tcase_add_test(tc_register_retry, Util_start_lds);
+    tcase_add_test(tc_register_retry, Util_wait_retry);
+    // check if there
+    tcase_add_test(tc_register_retry, Client_find_registered);
+    tcase_add_test(tc_register_retry, Server_unregister_periodic);
+    tcase_add_test(tc_register_retry, Client_find_discovery);
+    tcase_add_test(tc_register_retry, Util_stop_lds);
+
+    suite_add_tcase(s,tc_register_retry);
+
     TCase *tc_register_find = tcase_create("RegisterServer and FindServers");
     tcase_add_unchecked_fixture(tc_register_find, setup_lds, teardown_lds);
     tcase_add_unchecked_fixture(tc_register_find, setup_register, teardown_register);
     tcase_add_test(tc_register_find, Client_find_discovery);
     tcase_add_test(tc_register_find, Server_register);
     tcase_add_test(tc_register_find, Client_find_registered);
+    tcase_add_test(tc_register_find, Util_wait_mdns);
+    tcase_add_test(tc_register_find, Client_find_on_network_registered);
     tcase_add_test(tc_register_find, Client_find_filter);
+    tcase_add_test(tc_register_find, Client_get_endpoints);
+    tcase_add_test(tc_register_find, Client_filter_locale);
     tcase_add_test(tc_register_find, Server_unregister);
     tcase_add_test(tc_register_find, Client_find_discovery);
     tcase_add_test(tc_register_find, Client_filter_discovery);
@@ -260,6 +535,12 @@ static Suite* testSuite_Client(void) {
     tcase_add_test(tc_register_timeout, Client_find_registered);
     tcase_add_test(tc_register_timeout, Util_wait_timeout);
     tcase_add_test(tc_register_timeout, Client_find_discovery);
+    // now check if semaphore file works
+    tcase_add_test(tc_register_timeout, Server_register_semaphore);
+    tcase_add_test(tc_register_timeout, Client_find_registered);
+    tcase_add_test(tc_register_timeout, Server_unregister_semaphore);
+    tcase_add_test(tc_register_timeout, Util_wait_timeout);
+    tcase_add_test(tc_register_timeout, Client_find_discovery);
     suite_add_tcase(s,tc_register_timeout);
     return s;
 }

+ 2 - 1
tools/amalgamate.py

@@ -22,7 +22,7 @@ pos = outname.find(".")
 if pos > 0:
     outname = outname[:pos]
 include_re = re.compile("^#include (\".*\").*$")
-guard_re = re.compile("^#(?:(?:ifndef|define) [A-Z_]+_H_|endif /\* [A-Z_]+_H_ \*/|endif // [A-Z_]+_H_)")
+guard_re = re.compile("^#(?:(?:ifndef|define)\s*[A-Z_]+_H_|endif /\* [A-Z_]+_H_ \*/|endif // [A-Z_]+_H_|endif\s*/\*\s*!?[A-Z_]+_H[_]+\s*\*/)")
 
 print ("Starting amalgamating file "+ args.outfile)
 
@@ -47,6 +47,7 @@ file.write(u"""/* THIS IS A SINGLE-FILE DISTRIBUTION CONCATENATED FROM THE OPEN6
 if is_c:
     file.write(u'''#ifndef UA_DYNAMIC_LINKING_EXPORT
 # define UA_DYNAMIC_LINKING_EXPORT
+# define MDNSD_DYNAMIC_LINKING
 #endif
 
 #include "%s.h"

+ 33 - 20
tools/travis/travis_linux_script.sh

@@ -43,9 +43,9 @@ if [ $ANALYZE = "true" ]; then
     fi
     echo -en 'travis_fold:end:script.analyze\\r'
 else
-    echo "=== Building ==="
+    echo -en "\r\n=== Building ===\r\n"
 
-    echo "Documentation and certificate build"  && echo -en 'travis_fold:start:script.build.doc\\r'
+    echo -e "\r\n== Documentation and certificate build =="  && echo -en 'travis_fold:start:script.build.doc\\r'
     mkdir -p build
     cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_DOCUMENTATION=ON -DUA_BUILD_SELFSIGNED_CERTIFICATE=ON ..
@@ -57,18 +57,17 @@ else
     cp ./examples/server_cert.der ../../
     cd .. && rm build -rf
     echo -en 'travis_fold:end:script.build.doc\\r'
-    
-    echo "Full Namespace 0 Generation"  && echo -en 'travis_fold:start:script.build.ns0\\r'
+
+    echo -e "\r\n== Full Namespace 0 Generation ==" && echo -en 'travis_fold:start:script.build.ns0\\r'
     mkdir -p build
     cd build
     cmake -DCMAKE_BUILD_TYPE=Debug -DUA_ENABLE_GENERATE_NAMESPACE0=On -DUA_BUILD_EXAMPLES=ON  ..
     make -j
     cd .. && rm build -rf
     echo -en 'travis_fold:end:script.build.ns0\\r'
-    
     # cross compilation only with gcc
     if [ "$CC" = "gcc" ]; then
-        echo "Cross compile release build for MinGW 32 bit"  && echo -en 'travis_fold:start:script.build.cross_mingw32\\r'
+        echo -e "\r\n== Cross compile release build for MinGW 32 bit =="  && echo -en 'travis_fold:start:script.build.cross_mingw32\\r'
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw32.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON ..
         make -j
@@ -77,7 +76,7 @@ else
         cd .. && rm build -rf
     	echo -en 'travis_fold:end:script.build.cross_mingw32\\r'
 
-        echo "Cross compile release build for MinGW 64 bit"  && echo -en 'travis_fold:start:script.build.cross_mingw64\\r'
+        echo -e "\r\n== Cross compile release build for MinGW 64 bit =="  && echo -en 'travis_fold:start:script.build.cross_mingw64\\r'
         mkdir -p build && cd build
         cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-mingw64.cmake -DUA_ENABLE_AMALGAMATION=ON -DCMAKE_BUILD_TYPE=Release -DUA_BUILD_EXAMPLES=ON ..
         make -j
@@ -86,7 +85,7 @@ else
         cd .. && rm build -rf
     	echo -en 'travis_fold:end:script.build.cross_mingw64\\r'
 
-        echo "Cross compile release build for 32-bit linux"  && echo -en 'travis_fold:start:script.build.cross_linux\\r'
+        echo -e "\r\n== Cross compile release build for 32-bit linux =="  && echo -en 'travis_fold:start:script.build.cross_linux\\r'
         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_EXAMPLES=ON ..
         make -j
@@ -95,7 +94,7 @@ else
         cd .. && rm build -rf
     	echo -en 'travis_fold:end:script.build.cross_linux\\r'
 
-        echo "Cross compile release build for RaspberryPi"  && echo -en 'travis_fold:start:script.build.cross_raspi\\r'
+        echo -e "\r\n== Cross compile release build for RaspberryPi =="  && echo -en 'travis_fold:start:script.build.cross_raspi\\r'
         mkdir -p build && cd build
         git clone https://github.com/raspberrypi/tools
         export PATH=$PATH:./tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/
@@ -107,7 +106,7 @@ else
     	echo -en 'travis_fold:end:script.build.cross_raspi\\r'
     fi
 
-    echo "Compile release build for 64-bit linux"  && echo -en 'travis_fold:start:script.build.linux_64\\r'
+    echo -e "\r\n== Compile release build for 64-bit linux =="  && echo -en 'travis_fold:start:script.build.linux_64\\r'
     mkdir -p build && cd build
     cmake -DCMAKE_BUILD_TYPE=Release -DUA_ENABLE_AMALGAMATION=ON -DUA_BUILD_EXAMPLES=ON ..
     make -j
@@ -118,7 +117,7 @@ else
     cd .. && rm build -rf
 	echo -en 'travis_fold:end:script.build.linux_64\\r'
 
-    echo "Building the C++ example"  && echo -en 'travis_fold:start:script.build.example\\r'
+    echo -e "\r\n== Building the C++ example =="  && echo -en 'travis_fold:start:script.build.example\\r'
     mkdir -p build && cd build
     cp ../../open62541.* .
     gcc -std=c99 -c open62541.c
@@ -126,37 +125,51 @@ else
     cd .. && rm build -rf
 	echo -en 'travis_fold:end:script.build.example\\r'
 
-    echo "Compile multithreaded version" && echo -en 'travis_fold:start:script.build.multithread\\r'
+    echo -e "\r\n== Compile multithreaded version ==" && echo -en 'travis_fold:start:script.build.multithread\\r'
     mkdir -p build && cd build
     cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_BUILD_EXAMPLES=ON ..
     make -j
     cd .. && rm build -rf
+	echo -en 'travis_fold:end:script.build.multithread\\r'
+
+    echo -e "\r\n== Compile without discovery version ==" && echo -en 'travis_fold:start:script.build.unit_test_valgrind\\r'
+    mkdir -p build && cd build
+    cmake -DUA_ENABLE_DISCOVERY=OFF -DUA_ENABLE_DISCOVERY_MULTICAST=OFF -DUA_BUILD_EXAMPLES=ON ..
+    make -j
+    cd .. && rm build -rf
+
+    echo -e "\r\n== Compile discovery without multicast version =="
+    mkdir -p build && cd build
+    cmake -DUA_ENABLE_DISCOVERY=ON -DUA_ENABLE_DISCOVERY_MULTICAST=OFF -DUA_BUILD_EXAMPLES=ON ..
+    make -j
+    cd .. && rm build -rf
+
 
-    echo "Compile without discovery version"
+    echo -e "\r\n== Compile multithreaded version with discovery =="
     mkdir -p build && cd build
-    cmake -DUA_ENABLE_DISCOVERY=OFF -DUA_BUILD_EXAMPLES=ON ..
+    cmake -DUA_ENABLE_MULTITHREADING=ON -DUA_ENABLE_DISCOVERY=ON -DUA_ENABLE_DISCOVERY_MULTICAST=ON -DUA_BUILD_EXAMPLES=ON ..
     make -j
     cd .. && rm build -rf
 	echo -en 'travis_fold:end:script.build.multithread\\r'
 
-    echo "Debug build and unit tests (64 bit)" && echo -en 'travis_fold:start:script.build.unit_test_valgrind\\r'
+    echo -e "\r\n== Debug build and unit tests (64 bit) ==" && echo -en 'travis_fold:start:script.build.unit_test_valgrind\\r'
     mkdir -p build && cd build
-    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_VALGRIND_UNIT_TESTS=ON ..
+    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_DISCOVERY=ON -DUA_ENABLE_DISCOVERY_MULTICAST=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_VALGRIND_UNIT_TESTS=ON ..
     make -j && make test ARGS="-V"
     (valgrind --leak-check=yes --error-exitcode=3 ./bin/examples/server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
 	echo -en 'travis_fold:end:script.build.unit_test_valgrind\\r'
 
     # without valgrind
-    echo "Debug build and unit tests without valgrind" && echo -en 'travis_fold:start:script.build.unit_test\\r'
-    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_VALGRIND_UNIT_TESTS=OFF ..
+    echo -e "\r\n== Debug build and unit tests without valgrind ==" && echo -en 'travis_fold:start:script.build.unit_test\\r'
+    cmake -DCMAKE_BUILD_TYPE=Debug -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_DISCOVERY=ON -DUA_ENABLE_DISCOVERY_MULTICAST=ON -DUA_BUILD_UNIT_TESTS=ON -DUA_ENABLE_COVERAGE=ON -DUA_ENABLE_VALGRIND_UNIT_TESTS=OFF ..
     make -j && make test ARGS="-V"
     (./bin/examples/server & export pid=$!; sleep 2; kill -INT $pid; wait $pid);
 	echo -en 'travis_fold:end:script.build.unit_test\\r'
 
     # only run coveralls on main repo, otherwise it fails uploading the files
-    echo "-> Current repo: ${TRAVIS_REPO_SLUG}"
+    echo -e "\r\n== -> Current repo: ${TRAVIS_REPO_SLUG} =="
     if [ "$CC" = "gcc" ] && [ "${TRAVIS_REPO_SLUG}" = "open62541/open62541" ]; then
-        echo "  Building coveralls for ${TRAVIS_REPO_SLUG}" && echo -en 'travis_fold:start:script.build.coveralls\\r'
+         -en "\r\n==   Building coveralls for ${TRAVIS_REPO_SLUG} ==" && echo -en 'travis_fold:start:script.build.coveralls\\r'
         coveralls -E '.*\.h' -E '.*CMakeCXXCompilerId\.cpp' -E '.*CMakeCCompilerId\.c' -r ../ || true # ignore result since coveralls is unreachable from time to time
         echo -en 'travis_fold:end:script.build.coveralls\\r'
     fi