open62541-sys 0.6.1

Low-level, unsafe bindings for the C11 library open62541, an open source and free implementation of OPC UA (OPC Unified Architecture).
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
/* 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/.
 *
 *    Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
 *    Copyright 2014-2016 (c) Sten GrĂ¼ner
 *    Copyright 2014, 2017 (c) Florian Palm
 *    Copyright 2016 (c) Oleksiy Vasylyev
 *    Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
 *    Copyright 2017 (c) frax2222
 *    Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
 *    Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
 */

#include "ua_server_internal.h"
#include "ua_discovery.h"
#include "ua_services.h"

#ifdef UA_ENABLE_DISCOVERY

#include <open62541/client.h>

static UA_StatusCode
setApplicationDescriptionFromRegisteredServer(const UA_FindServersRequest *request,
                                              UA_ApplicationDescription *target,
                                              const UA_RegisteredServer *rs) {
    UA_StatusCode retval =  UA_STATUSCODE_GOOD;
    target->applicationType = rs->serverType;
    retval |= UA_String_copy(&rs->serverUri, &target->applicationUri);
    retval |= UA_String_copy(&rs->productUri, &target->productUri);
    retval |= UA_String_copy(&rs->gatewayServerUri, &target->gatewayServerUri);
    if(retval != UA_STATUSCODE_GOOD)
        return retval;

    /* If the client requests a specific locale, select the corresponding server
     * name */
    if(request->localeIdsSize) {
        UA_Boolean appNameFound = false;
        for(size_t i = 0; i < request->localeIdsSize && !appNameFound; i++) {
            for(size_t j =0; j < rs->serverNamesSize; j++) {
                if(UA_String_equal(&request->localeIds[i],
                                   &rs->serverNames[j].locale)) {
                    retval = UA_LocalizedText_copy(&rs->serverNames[j],
                                                   &target->applicationName);
                    if(retval != UA_STATUSCODE_GOOD)
                        return retval;
                    appNameFound = true;
                    break;
                }
            }
        }

        /* Server does not have the requested local, therefore we can select the
         * most suitable one */
        if(!appNameFound && rs->serverNamesSize) {
            retval = UA_LocalizedText_copy(&rs->serverNames[0],
                                           &target->applicationName);
            if(retval != UA_STATUSCODE_GOOD)
                return retval;
        }
    } else if(rs->serverNamesSize) {
        /* Just take the first name */
        retval = UA_LocalizedText_copy(&rs->serverNames[0],
                                       &target->applicationName);
        if(retval != UA_STATUSCODE_GOOD)
            return retval;
    }

    /* TODO: Where do we get the discoveryProfileUri for application data? */

    if(rs->discoveryUrlsSize > 0) {
        target->discoveryUrls = (UA_String *)
            UA_calloc(rs->discoveryUrlsSize, sizeof(UA_String));
        if(!target->discoveryUrls)
            return UA_STATUSCODE_BADOUTOFMEMORY;
        target->discoveryUrlsSize = rs->discoveryUrlsSize;
        for(size_t i = 0; i < rs->discoveryUrlsSize; i++)
            retval |= UA_String_copy(&rs->discoveryUrls[i],
                                     &target->discoveryUrls[i]);
    }

    return retval;
}
#endif

UA_Boolean
Service_FindServers(UA_Server *server, UA_Session *session,
                    const UA_FindServersRequest *request,
                    UA_FindServersResponse *response) {
    UA_ServerConfig *sc = &server->config;
    UA_LOG_DEBUG_SESSION(sc->logging, session, "Processing FindServersRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);

    /* Return the server itself? */
    UA_Boolean foundSelf = false;
    if(request->serverUrisSize) {
        for(size_t i = 0; i < request->serverUrisSize; i++) {
            if(UA_String_equal(&request->serverUris[i],
                               &sc->applicationDescription.applicationUri)) {
                foundSelf = true;
                break;
            }
        }
    } else {
        foundSelf = true;
    }

#ifndef UA_ENABLE_DISCOVERY
    if(!foundSelf)
        return true;

    response->responseHeader.serviceResult =
        UA_Array_copy(&sc->applicationDescription, 1, (void**)&response->servers,
                      &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
        return true;

    response->serversSize = 1;
#else
    UA_DiscoveryManager *dm = (UA_DiscoveryManager*)
        getServerComponentByName(server, UA_STRING("discovery"));
    if(!dm) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
        return true;
    }

    /* Allocate enough memory, including memory for the "self" response */
    size_t maxResults = dm->registeredServersSize + 1;
    response->servers = (UA_ApplicationDescription*)
        UA_Array_new(maxResults, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
    if(!response->servers) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
        return true;
    }

    size_t pos = 0;
    registeredServer *current;

    /* Copy "self" ApplicationDescriptions into the response */
    if(foundSelf) {
        response->responseHeader.serviceResult =
            UA_ApplicationDescription_copy(&sc->applicationDescription,
                                           &response->servers[pos]);
        pos++;
        if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
            goto cleanup;
    }

    /* Copy registered ApplicationDescriptions into the response */
    LIST_FOREACH(current, &dm->registeredServers, pointers) {
        UA_Boolean usable = (request->serverUrisSize == 0);
        if(!usable) {
            /* If client only requested a specific set of servers */
            for(size_t i = 0; i < request->serverUrisSize; i++) {
                if(UA_String_equal(&current->registeredServer.serverUri,
                                   &request->serverUris[i])) {
                    usable = true;
                    break;
                }
            }
        }

        if(!usable)
            continue;

        response->responseHeader.serviceResult |=
            setApplicationDescriptionFromRegisteredServer(request,
                                                          &response->servers[pos],
                                                          &current->registeredServer);
        pos++;
    }

 cleanup:

    /* Set the final size */
    if(pos == 0) {
        UA_free(response->servers);
        response->servers = NULL;
    }
    response->serversSize = pos;
#endif

    /* Mirror back the expected EndpointUrl */
    if(request->endpointUrl.length > 0) {
        for(size_t i = 0; i < response->serversSize; i++) {
            UA_ApplicationDescription *ad = &response->servers[i];
            UA_Array_delete(ad->discoveryUrls, ad->discoveryUrlsSize,
                            &UA_TYPES[UA_TYPES_STRING]);
            ad->discoveryUrls = NULL;
            ad->discoveryUrlsSize = 0;
            response->responseHeader.serviceResult =
                UA_Array_copy(&request->endpointUrl, 1,
                              (void**)&ad->discoveryUrls,
                              &UA_TYPES[UA_TYPES_STRING]);
            if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
                break;
            ad->discoveryUrlsSize = 1;
        }
    }

    return true;
}

#if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST)
/* All filter criteria must be fulfilled in the list entry. The comparison is
 * case insensitive. Returns true if the entry matches the filter. */
static UA_Boolean
entryMatchesCapabilityFilter(size_t serverCapabilityFilterSize,
                             UA_String *serverCapabilityFilter,
                             UA_ServerOnNetwork *current) {
    /* If the entry has less capabilities defined than the filter, there's no match */
    if(serverCapabilityFilterSize > current->serverCapabilitiesSize)
        return false;
    for(size_t i = 0; i < serverCapabilityFilterSize; i++) {
        UA_Boolean capabilityFound = false;
        for(size_t j = 0; j < current->serverCapabilitiesSize; j++) {
            if(UA_String_equal_ignorecase(&serverCapabilityFilter[i],
                               &current->serverCapabilities[j])) {
                capabilityFound = true;
                break;
            }
        }
        if(!capabilityFound)
            return false;
    }
    return true;
}

UA_Boolean
Service_FindServersOnNetwork(UA_Server *server, UA_Session *session,
                             const UA_FindServersOnNetworkRequest *request,
                             UA_FindServersOnNetworkResponse *response) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    UA_DiscoveryManager *dm = (UA_DiscoveryManager*)
        getServerComponentByName(server, UA_STRING("discovery"));
    if(!dm) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
        return true;
    }

    if(!server->config.mdnsEnabled) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTIMPLEMENTED;
        return true;
    }

    /* Set LastCounterResetTime */
    response->lastCounterResetTime =
        UA_DiscoveryManager_getServerOnNetworkCounterResetTime(dm);

    /* Compute the max number of records to return */
    UA_UInt32 recordCount = 0;
    UA_UInt32 serverOnNetworkRecordIdCounter =
        UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(dm);
    if(request->startingRecordId < serverOnNetworkRecordIdCounter)
        recordCount = serverOnNetworkRecordIdCounter - request->startingRecordId;
    if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn)
        recordCount = UA_MIN(recordCount, request->maxRecordsToReturn);
    if(recordCount == 0) {
        response->serversSize = 0;
        return true;
    }

    /* Iterate over all records and add to filtered list */
    UA_UInt32 filteredCount = 0;
    UA_STACKARRAY(UA_ServerOnNetwork*, filtered, recordCount);
    UA_ServerOnNetwork *current = UA_DiscoveryManager_getServerOnNetworkList(dm);
    if(!current) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
        return true;
    }
    for(size_t i = 0; i < recordCount; i++) {
        if(filteredCount >= recordCount)
            break;
        if(current->recordId < request->startingRecordId)
            continue;
        if(!entryMatchesCapabilityFilter(request->serverCapabilityFilterSize,
                               request->serverCapabilityFilter, current))
            continue;
        filtered[filteredCount++] = current;
        current = UA_DiscoveryManager_getNextServerOnNetworkRecord(dm, current);
        if(!current)
            break;
    }

    if(filteredCount == 0)
        return true;

    /* Allocate the array for the response */
    response->servers = (UA_ServerOnNetwork*)
        UA_malloc(sizeof(UA_ServerOnNetwork)*filteredCount);
    if(!response->servers) {
        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
        return true;
    }
    response->serversSize = filteredCount;

    /* Copy the server names */
    for(size_t i = 0; i < filteredCount; i++) {
        UA_ServerOnNetwork_copy(filtered[i], &response->servers[filteredCount-i-1]);
    }
    return true;
}
#endif

static UA_String basic256Sha256Uri = UA_STRING_STATIC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");

/* Get an encrypted policy or NULL if no encrypted policy is defined */
UA_SecurityPolicy *
getDefaultEncryptedSecurityPolicy(UA_Server *server,
                                  UA_SecurityPolicyType type) {
    UA_SecurityPolicy *best = NULL;
    UA_Byte securityLevel = 0;

    (void)type;
    for(size_t i = 0; i < server->config.securityPoliciesSize; i++) {
        UA_SecurityPolicy *sp = &server->config.securityPolicies[i];
        if(sp->policyType == UA_SECURITYPOLICYTYPE_NONE)
            continue;
        /* This SecurityPolicy is used to secure a UserIdentityToken on top of a
         * #None SecureChannel (the only situation this function is called for).
         * ECC and RSA-DH policies use ephemeral key agreement that must be
         * bound to a secured SecureChannel - they must never be used to secure
         * an auth token over #None. Only the static-RSA encryption policies
         * (e.g. Basic256Sha256, Aes*_RsaOaep/RsaPss) qualify. */
        if(UA_SecurityPolicy_isEcc(sp) || UA_SecurityPolicy_isEnhancedSecurity(sp))
            continue;
        /* Return early with Basic256Sha256 when available. "Secure enough" and
         * most clients support it.*/
        if(UA_String_equal(&basic256Sha256Uri, &sp->policyUri))
            return sp;
        if(sp->securityLevel >= securityLevel) {
            best = sp;
            securityLevel = sp->securityLevel;
        }
    }
    return best;
}

static const char *securityModeStrs[4] = {"-invalid", "-none", "-sign", "-sign+encrypt"};

UA_String
securityPolicyUriPostfix(const UA_String uri) {
    if(uri.length == 0) return uri;
    for(UA_Byte *b = uri.data + uri.length - 1; b >= uri.data; b--) {
        if(*b != '#')
            continue;
        UA_String postfix = {uri.length - (size_t)(b - uri.data), b};
        return postfix;
    }
    return uri;
}

static UA_StatusCode
updateEndpointUserIdentityToken(UA_Server *server,
                                UA_SecurityPolicyType policyType,
                                UA_EndpointDescription *ed) {
    /* Don't modify the UserIdentityTokens if there are manually configured
     * entries */
    if(ed->userIdentityTokensSize > 0)
        return UA_STATUSCODE_GOOD;

    /* Copy the UserTokenPolicies from the AccessControl plugin, but only the
     * matching ones to the securityPolicyUri.
     * TODO: Different instances of the AccessControl plugin per Endpoint */
    UA_StatusCode res = UA_STATUSCODE_GOOD;
    UA_ServerConfig *sc = &server->config;
    for(size_t i = 0; i < sc->accessControl.userTokenPoliciesSize; i++) {
        UA_UserTokenPolicy *utp = &sc->accessControl.userTokenPolicies[i];

        /* Append the UserTokenPolicy from the AccesssControl plugin */
        res = UA_Array_appendCopy((void**)&ed->userIdentityTokens,
                                  &ed->userIdentityTokensSize, utp,
                                  &UA_TYPES[UA_TYPES_USERTOKENPOLICY]);
        if(res != UA_STATUSCODE_GOOD)
            return res;

        /* Now we modify the freshly copied last entry and ignore whatever
         * SecurityPolicy was set in sc->accessControl.userTokenPolicies and
         * choose something appropriate. If empty, the SecurityPolicy of the
         * SecureChannel is used. */
        utp = &ed->userIdentityTokens[ed->userIdentityTokensSize - 1];
        UA_String_clear(&utp->securityPolicyUri);

#ifdef UA_ENABLE_ENCRYPTION
        /* Anonymous tokens don't need encryption. All other tokens require
         * encryption with the exception of Username/Password if also the
         * allowNonePolicyPassword option has been set. The same logic is used
         * in selectEndpointAndTokenPolicy (ua_services_session.c). */
        if(utp->tokenType != UA_USERTOKENTYPE_ANONYMOUS &&
           UA_String_equal(&ed->securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) &&
           (!sc->allowNonePolicyPassword || utp->tokenType != UA_USERTOKENTYPE_USERNAME)) {
            /* Use the SecurityPolicy for the SecureChannel also for the
             * username/password. Otherwise pick the "bĂ«st" SecurityPolicĂ¿. */
            UA_SecurityPolicy *encSP;
            if(ed->securityMode == UA_MESSAGESECURITYMODE_NONE)
                encSP = getDefaultEncryptedSecurityPolicy(server, policyType);
            else
                encSP = getSecurityPolicyByUri(server, &ed->securityPolicyUri);
            if(!encSP) {
                /* No encrypted SecurityPolicy available */
                UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT,
                               "Removing a UserTokenPolicy that would allow the "
                               "password to be transmitted without encryption "
                               "(Can be enabled via config->allowNonePolicyPassword)");
                UA_StatusCode res2 =
                    UA_Array_resize((void **)&ed->userIdentityTokens,
                                    &ed->userIdentityTokensSize,
                                    ed->userIdentityTokensSize - 1,
                                    &UA_TYPES[UA_TYPES_USERTOKENPOLICY]);
                (void)res2;
                continue;
            }
            res |= UA_String_copy(&encSP->policyUri, &utp->securityPolicyUri);
        }
#endif

        /* Append the SecurityMode and SecurityPolicy postfix to the PolicyId to
         * make it unique */
        UA_String postfix;
        if(utp->securityPolicyUri.length > 0)
            postfix = securityPolicyUriPostfix(utp->securityPolicyUri);
        else
            postfix = securityPolicyUriPostfix(ed->securityPolicyUri);
        size_t newLen = utp->policyId.length + postfix.length +
            strlen(securityModeStrs[ed->securityMode]);
        UA_Byte *newString = (UA_Byte*)UA_realloc(utp->policyId.data, newLen);
        if(!newString)
            continue;
        size_t pos = utp->policyId.length;
        memcpy(&newString[pos], securityModeStrs[ed->securityMode],
               strlen(securityModeStrs[ed->securityMode]));
        pos += strlen(securityModeStrs[ed->securityMode]);
        memcpy(&newString[pos], postfix.data, postfix.length);
        utp->policyId.data = newString;
        utp->policyId.length = newLen;
    }

    return res;
}

/* Also reused to create the EndpointDescription array in the
 * CreateSessionResponse */
UA_StatusCode
setCurrentEndpointsArray(UA_Server *server, const UA_String endpointUrl,
                         UA_String *profileUris, size_t profileUrisSize,
                         UA_EndpointDescription **arr, size_t *arrSize) {
    UA_ServerConfig *sc = &server->config;

    /* Clone the endpoint for each discoveryURL? */
    size_t clone_times = 1;
    if(endpointUrl.length == 0)
        clone_times = sc->applicationDescription.discoveryUrlsSize;

    /* Allocate the array */
    *arr = (UA_EndpointDescription*)
        UA_Array_new(sc->endpointsSize * clone_times,
                     &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
    if(!*arr)
        return UA_STATUSCODE_BADOUTOFMEMORY;

    size_t pos = 0;
    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    for(size_t j = 0; j < sc->endpointsSize; ++j) {
        const UA_EndpointDescription *ep = &sc->endpoints[j];

        /* Test if the supported binary profile shall be returned */
        UA_Boolean usable = (profileUrisSize == 0);
        if(!usable) {
            for(size_t i = 0; i < profileUrisSize; ++i) {
                if(!UA_String_equal(&profileUris[i], &ep->transportProfileUri))
                    continue;
                usable = true;
                break;
            }
        }
        if(!usable)
            continue;

        /* Get the SecurityPolicy */
        UA_SecurityPolicy *sp =
            getSecurityPolicyByUri(server, &ep->securityPolicyUri);
        if(!sp) {
            UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
                           "GetEndpoints: Endpoint defines SecurityPolicy "
                           "%S which is not available", ep->securityPolicyUri);
            continue;
        }

        /* Copy into the results */
        for(size_t i = 0; i < clone_times; ++i) {
            /* Copy the endpoint with a current ApplicationDescription */
            UA_EndpointDescription *ed = &(*arr)[pos];
            retval |= UA_EndpointDescription_copy(&sc->endpoints[j], ed);
            UA_ApplicationDescription_clear(&ed->server);
            retval |= UA_ApplicationDescription_copy(&sc->applicationDescription,
                                                     &ed->server);

            /* Set the local certificate configured for the SecurityPolicy */
            UA_ByteString_clear(&ed->serverCertificate);
            retval |= UA_ByteString_copy(&sp->localCertificate,
                                         &ed->serverCertificate);

            /* Set the User Identity Token list from the AccessControl plugin.
             * This also selects an appropriate SecurityPolicy for the
             * AuthenticationToken. */
            retval |= updateEndpointUserIdentityToken(server, sp->policyType, ed);

            /* OPC UA Part 4 §5.4.2:
             *
             * If the endpoint uses None security but a token policy requires
             * encryption, the client needs a certificate to encrypt the token.
             * Set serverCertificate from the first token policy's encryption
             * SecurityPolicy so the client can encrypt the credential. */
            if(ed->serverCertificate.length == 0) {
                for(size_t ti = 0; ti < ed->userIdentityTokensSize; ti++) {
                    UA_UserTokenPolicy *utp = &ed->userIdentityTokens[ti];
                    if(utp->securityPolicyUri.length == 0)
                        continue;
                    UA_SecurityPolicy *encSP =
                        getSecurityPolicyByUri(server, &utp->securityPolicyUri);
                    if(!encSP || encSP->localCertificate.length == 0)
                        continue;
                    retval |= UA_ByteString_copy(&encSP->localCertificate,
                                                 &ed->serverCertificate);
                    break;
                }
            }

            /* Set the EndpointURL */
            UA_String_clear(&ed->endpointUrl);
            if(endpointUrl.length == 0) {
                retval |= UA_String_copy(&sc->applicationDescription.discoveryUrls[i],
                                         &ed->endpointUrl);
            } else {
                /* Mirror back the requested EndpointUrl and also add it to the
                 * array of discovery urls */
                retval |= UA_String_copy(&endpointUrl, &ed->endpointUrl);

                /* Check if the ServerUrl is already present in the DiscoveryUrl
                 * array */
                size_t k = 0;
                for(; k < ed->server.discoveryUrlsSize; k++) {
                    if(UA_String_equal(&ed->endpointUrl, &ed->server.discoveryUrls[k]))
                        break;
                }
                if(k == ed->server.discoveryUrlsSize) {
                    retval |= UA_Array_appendCopy((void **)&ed->server.discoveryUrls,
                                                  &ed->server.discoveryUrlsSize,
                                                  &endpointUrl,
                                                  &UA_TYPES[UA_TYPES_STRING]);
                }
            }
            if(retval != UA_STATUSCODE_GOOD)
                goto error;

            pos++;
        }
    }

    *arrSize = pos;
    return UA_STATUSCODE_GOOD;

 error:
    UA_Array_delete(*arr, sc->endpointsSize * clone_times,
                    &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
    *arr = NULL;
    return retval;
}

UA_Boolean
Service_GetEndpoints(UA_Server *server, UA_Session *session,
                     const UA_GetEndpointsRequest *request,
                     UA_GetEndpointsResponse *response) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing GetEndpointsRequest with endpointUrl %S",
                         request->endpointUrl);

    /* If the client expects to see a specific endpointurl, mirror it back. If
     * not, clone the endpoints with the discovery url of all networklayers. */
    response->responseHeader.serviceResult =
        setCurrentEndpointsArray(server, request->endpointUrl,
                                 request->profileUris, request->profileUrisSize,
                                 &response->endpoints, &response->endpointsSize);
    return true;
}

#ifdef UA_ENABLE_DISCOVERY

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) {
    UA_LOCK_ASSERT(&server->serviceMutex);

    UA_DiscoveryManager *dm = (UA_DiscoveryManager*)
        getServerComponentByName(server, UA_STRING("discovery"));
    if(!dm)
        return;

    UA_ServerConfig *sc = &server->config;
    if(sc->applicationDescription.applicationType != UA_APPLICATIONTYPE_DISCOVERYSERVER) {
        responseHeader->serviceResult = UA_STATUSCODE_BADSERVICEUNSUPPORTED;
        return;
    }

    /* Find the server from the request in the registered list */
    registeredServer *rs = NULL;
    LIST_FOREACH(rs, &dm->registeredServers, pointers) {
        if(UA_String_equal(&rs->registeredServer.serverUri, &requestServer->serverUri))
            break;
    }

    UA_MdnsDiscoveryConfiguration *mdnsConfig = NULL;

    const UA_String* mdnsServerName = NULL;
    if(requestDiscoveryConfigurationSize) {
        *responseConfigurationResults =
            (UA_StatusCode *)UA_Array_new(requestDiscoveryConfigurationSize,
                                          &UA_TYPES[UA_TYPES_STATUSCODE]);
        if(!(*responseConfigurationResults)) {
            responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
            return;
        }
        *responseConfigurationResultsSize = requestDiscoveryConfigurationSize;

        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(!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
        char* filePath = (char*)
            UA_malloc(sizeof(char)*requestServer->semaphoreFilePath.length+1);
        if(!filePath) {
            UA_LOG_ERROR_SESSION(sc->logging, session,
                                 "Cannot allocate memory for semaphore path. "
                                 "Out of memory.");
            responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
            return;
        }
        memcpy(filePath, requestServer->semaphoreFilePath.data,
               requestServer->semaphoreFilePath.length );
        filePath[requestServer->semaphoreFilePath.length] = '\0';
        if(!UA_fileExists( filePath )) {
            responseHeader->serviceResult = UA_STATUSCODE_BADSEMAPHOREFILEMISSING;
            UA_free(filePath);
            return;
        }
        UA_free(filePath);
#else
        UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT,
                       "Ignoring semaphore file path. open62541 not compiled "
                       "with UA_ENABLE_DISCOVERY_SEMAPHORE=ON");
#endif
    }

#ifdef UA_ENABLE_DISCOVERY_MULTICAST
    if(sc->mdnsEnabled) {
        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_updateMdnsForDiscoveryUrl(dm, *mdnsServerName, mdnsConfig,
                                                   requestServer->discoveryUrls[i],
                                                   requestServer->isOnline, updateTxt);
        }
    }
#endif

    if(!requestServer->isOnline) {
        /* Server is shutting down. Remove it from the registered servers list */
        if(!rs) {
            /* Server not found, show warning */
            UA_LOG_WARNING_SESSION(sc->logging, session,
                                   "Could not unregister server %S. Not registered.",
                                   requestServer->serverUri);
            responseHeader->serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
            return;
        }

        if(dm->registerServerCallback)
            dm->registerServerCallback(requestServer, dm->registerServerCallbackData);

        /* Server found, remove from list */
        LIST_REMOVE(rs, pointers);
        UA_RegisteredServer_clear(&rs->registeredServer);
        UA_free(rs);
        dm->registeredServersSize--;
        responseHeader->serviceResult = UA_STATUSCODE_GOOD;
        return;
    }

    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    if(!rs) {
        /* Server not yet registered, register it by adding it to the list */
        UA_LOG_DEBUG_SESSION(sc->logging, session,
                             "Registering new server: %S",
                             requestServer->serverUri);

        rs = (registeredServer*)UA_malloc(sizeof(registeredServer));
        if(!rs) {
            responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
            return;
        }

        LIST_INSERT_HEAD(&dm->registeredServers, rs, pointers);
        dm->registeredServersSize++;
    } else {
        UA_RegisteredServer_clear(&rs->registeredServer);
    }

    /* Always call the callback, if it is set. Previously we only called it if
     * it was a new register call. It may be the case that this endpoint
     * registered before, then crashed, restarts and registeres again. In that
     * case the entry is not deleted and the callback would not be called. */
    if(dm->registerServerCallback)
        dm->registerServerCallback(requestServer,
                                   dm->registerServerCallbackData);

    /* Ccopy the data from the request into the list */
    UA_EventLoop *el = sc->eventLoop;
    UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
    UA_RegisteredServer_copy(requestServer, &rs->registeredServer);
    rs->lastSeen = nowMonotonic;
    responseHeader->serviceResult = retval;
}

UA_Boolean
Service_RegisterServer(UA_Server *server, UA_Session *session,
                       const UA_RegisterServerRequest *request,
                       UA_RegisterServerResponse *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing RegisterServerRequest");
    UA_LOCK_ASSERT(&server->serviceMutex);
    process_RegisterServer(server, session, &request->requestHeader,
                           &request->server, 0, NULL, &response->responseHeader,
                           0, NULL, 0, NULL);
    return true;
}

UA_Boolean
Service_RegisterServer2(UA_Server *server, UA_Session *session,
                        const UA_RegisterServer2Request *request,
                        UA_RegisterServer2Response *response) {
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
                         "Processing RegisterServer2Request");
    UA_LOCK_ASSERT(&server->serviceMutex);
    process_RegisterServer(server, session, &request->requestHeader,
                           &request->server,
                           request->discoveryConfigurationSize,
                           request->discoveryConfiguration,
                           &response->responseHeader,
                           &response->configurationResultsSize,
                           &response->configurationResults,
                           &response->diagnosticInfosSize,
                           response->diagnosticInfos);
    return true;
}

#endif /* UA_ENABLE_DISCOVERY */