Coverage Report

Created: 2026-08-31 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541/src/server/ua_server_async.c
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
 *
5
 *    Copyright 2019 (c) Fraunhofer IOSB (Author: Klaus Schick)
6
 *    Copyright 2019, 2025 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
7
 *    Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer)
8
 */
9
10
#include "ua_server_internal.h"
11
12
/* The layout of the results array is is:
13
 * [results-array] | padding | UA_AsyncResponse | padding | [UA_AsyncOperation]
14
 *
15
 * We need to take care about memory alignment (padding). */
16
static void *
17
allocateResultsArray(const UA_DataType *resultsType, size_t resultsLen,
18
                     UA_AsyncResponse **resp, UA_AsyncOperation **ops) {
19
    uintptr_t align = sizeof(size_t);
20
    size_t arrEnd = resultsType->memSize * resultsLen;
21
    uintptr_t responseBegin = (arrEnd + align - 1) & ~(align - 1);
22
    uintptr_t responseEnd = responseBegin + sizeof(UA_AsyncResponse);
23
    uintptr_t opsBegin = (responseEnd + align - 1) & ~(align - 1);
24
    uintptr_t opsEnd =  opsBegin + (sizeof(UA_AsyncOperation) * resultsLen);
25
    void *arr = UA_calloc(1, opsEnd);
26
    if(!arr)
27
        return NULL;
28
    uintptr_t arrMem = (uintptr_t)arr;
29
    *resp = (UA_AsyncResponse*)(arrMem + responseBegin);
30
    *ops = (UA_AsyncOperation*)(arrMem + opsBegin);
31
    return arr;
32
}
33
34
/* Cancel the operation, but don't _clear it here */
35
static void
36
UA_AsyncOperation_cancel(UA_Server *server, UA_AsyncOperation *op,
37
0
                         UA_StatusCode opstatus) {
38
0
    UA_ServerConfig *sc = &server->config;
39
0
    void *cancelPtr = NULL;
40
41
    /* Set the status and get the pointer that identifies the operation */
42
0
    switch(op->asyncOperationType) {
43
0
    case UA_ASYNCOPERATIONTYPE_READ_REQUEST:
44
0
        cancelPtr = op->output.read;
45
0
        op->output.read->hasStatus = true;
46
0
        op->output.read->status = opstatus;
47
0
        break;
48
0
    case UA_ASYNCOPERATIONTYPE_READ_DIRECT:
49
0
        cancelPtr = &op->output.directRead;
50
0
        op->output.directRead.hasStatus = true;
51
0
        op->output.directRead.status = opstatus;
52
0
        break;
53
0
    case UA_ASYNCOPERATIONTYPE_WRITE_REQUEST:
54
0
        cancelPtr = &op->context.writeValue.value;
55
0
        *op->output.write = opstatus;
56
0
        break;
57
0
    case UA_ASYNCOPERATIONTYPE_WRITE_DIRECT:
58
0
        cancelPtr = &op->context.writeValue.value;
59
0
        op->output.directWrite = opstatus;
60
0
        break;
61
0
    case UA_ASYNCOPERATIONTYPE_CALL_REQUEST:
62
        /* outputArguments is always an allocated pointer, also if the length is zero */
63
0
        cancelPtr = op->output.call->outputArguments;
64
0
        op->output.call->statusCode = opstatus;
65
0
        break;
66
0
    case UA_ASYNCOPERATIONTYPE_CALL_DIRECT:
67
        /* outputArguments is always an allocated pointer, also if the length is zero */
68
0
        cancelPtr = op->output.directCall.outputArguments;
69
0
        op->output.directCall.statusCode = opstatus;
70
0
        break;
71
0
    default: UA_assert(false); return;
72
0
    }
73
74
    /* Notify the application that it must no longer set the async result */
75
0
    if(sc->asyncOperationCancelCallback)
76
0
        sc->asyncOperationCancelCallback(server, cancelPtr);
77
0
}
78
79
static void
80
0
UA_AsyncOperation_delete(UA_AsyncOperation *op) {
81
0
    UA_assert(op->asyncOperationType >= UA_ASYNCOPERATIONTYPE_CALL_DIRECT);
82
0
    switch(op->asyncOperationType) {
83
0
    case UA_ASYNCOPERATIONTYPE_READ_DIRECT:
84
0
        UA_DataValue_clear(&op->output.directRead);
85
0
        break;
86
0
    case UA_ASYNCOPERATIONTYPE_WRITE_DIRECT:
87
0
        break;
88
0
    case UA_ASYNCOPERATIONTYPE_CALL_DIRECT:
89
0
        UA_CallMethodResult_clear(&op->output.directCall);
90
0
        break;
91
0
    default: UA_assert(false); break;
92
0
    }
93
0
    UA_free(op);
94
0
}
95
96
static void
97
0
UA_AsyncResponse_delete(UA_AsyncResponse *ar) {
98
0
    UA_NodeId_clear(&ar->sessionId);
99
100
    /* Clean up the results array last. Because the results array memory also
101
     * includes ar. */
102
0
    void *arr = NULL;
103
0
    size_t arrSize = 0;
104
0
    const UA_DataType *arrType;
105
0
    if(ar->responseType == &UA_TYPES[UA_TYPES_CALLRESPONSE]) {
106
0
        arr = ar->response.callResponse.results;
107
0
        arrSize = ar->response.callResponse.resultsSize;
108
0
        ar->response.callResponse.results = NULL;
109
0
        ar->response.callResponse.resultsSize = 0;
110
0
        arrType = &UA_TYPES[UA_TYPES_CALLMETHODRESULT];
111
0
    } else if(ar->responseType == &UA_TYPES[UA_TYPES_READRESPONSE]) {
112
0
        arr = ar->response.readResponse.results;
113
0
        arrSize = ar->response.readResponse.resultsSize;
114
0
        ar->response.readResponse.results = NULL;
115
0
        ar->response.readResponse.resultsSize = 0;
116
0
        arrType = &UA_TYPES[UA_TYPES_DATAVALUE];
117
0
    } else /* if(ar->responseType == &UA_TYPES[UA_TYPES_WRITERESPONSE]) */ {
118
0
        UA_assert(ar->responseType == &UA_TYPES[UA_TYPES_WRITERESPONSE]);
119
0
        arr = ar->response.writeResponse.results;
120
0
        arrSize = ar->response.writeResponse.resultsSize;
121
0
        ar->response.writeResponse.results = NULL;
122
0
        ar->response.writeResponse.resultsSize = 0;
123
0
        arrType = &UA_TYPES[UA_TYPES_STATUSCODE];
124
0
    }
125
0
    UA_clear(&ar->response.callResponse, ar->responseType);
126
0
    UA_Array_delete(arr, arrSize, arrType);
127
0
}
128
129
static void
130
notifyServiceEnd(UA_Server *server, UA_AsyncResponse *ar,
131
0
                 UA_Session *session, UA_SecureChannel *sc) {
132
    /* Collect the payload */
133
0
    UA_NodeId sessionId = (session) ? session->sessionId : UA_NODEID_NULL;
134
0
    UA_UInt32 secureChannelId = (sc) ? sc->securityToken.channelId : 0;
135
0
    UA_NodeId serviceTypeId;
136
0
    if(ar->responseType == &UA_TYPES[UA_TYPES_CALLRESPONSE]) {
137
0
        serviceTypeId = UA_TYPES[UA_TYPES_CALLREQUEST].typeId;
138
0
    } else if(ar->responseType == &UA_TYPES[UA_TYPES_READRESPONSE]) {
139
0
        serviceTypeId = UA_TYPES[UA_TYPES_READREQUEST].typeId;
140
0
    } else /* if(ar->responseType == &UA_TYPES[UA_TYPES_WRITERESPONSE]) */ {
141
0
        serviceTypeId = UA_TYPES[UA_TYPES_WRITEREQUEST].typeId;
142
0
    }
143
144
    /* Notify the application */
145
0
    UA_STATIC_THREAD_LOCAL UA_KeyValuePair notifyPayload[4] = {
146
0
        {{0, UA_STRING_STATIC("securechannel-id")}, {0}},
147
0
        {{0, UA_STRING_STATIC("session-id")}, {0}},
148
0
        {{0, UA_STRING_STATIC("request-id")}, {0}},
149
0
        {{0, UA_STRING_STATIC("service-type")}, {0}}
150
0
    };
151
0
    UA_KeyValueMap notifyPayloadMap = {4, notifyPayload};
152
0
    UA_Variant_setScalar(&notifyPayload[0].value, &secureChannelId,
153
0
                         &UA_TYPES[UA_TYPES_UINT32]);
154
0
    UA_Variant_setScalar(&notifyPayload[1].value, &sessionId,
155
0
                         &UA_TYPES[UA_TYPES_NODEID]);
156
0
    UA_Variant_setScalar(&notifyPayload[2].value, &ar->uacpRequestId,
157
0
                         &UA_TYPES[UA_TYPES_UINT32]);
158
0
    UA_Variant_setScalar(&notifyPayload[3].value, &serviceTypeId,
159
0
                         &UA_TYPES[UA_TYPES_NODEID]);
160
161
0
    UA_ApplicationNotificationType nt = UA_APPLICATIONNOTIFICATIONTYPE_SERVICE_END;
162
0
    notifyApplication(server, nt, notifyPayloadMap);
163
0
}
164
165
static void
166
0
sendAsyncResponse(UA_Server *server, UA_AsyncResponse *ar) {
167
0
    UA_assert(ar->opCountdown == 0);
168
169
0
    if(ar->abandoned) {
170
0
        UA_LOG_DEBUG(server->config.logging, UA_LOGCATEGORY_SERVER,
171
0
                     "Async response for closed transport carrier token %"
172
0
                     PRIu64 " was abandoned", ar->responseToken);
173
0
        return;
174
0
    }
175
176
    /* Get the session */
177
0
    UA_Session *session = getSessionById(server, &ar->sessionId);
178
0
    UA_SecureChannel *channel = (session) ? session->channel : NULL;
179
180
    /* Notify that processing the service has ended */
181
0
    notifyServiceEnd(server, ar, session, channel);
182
183
    /* Check the session */
184
0
    if(!session) {
185
0
        UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
186
0
                       "Async Service: Session %N no longer exists", ar->sessionId);
187
0
        return;
188
0
    }
189
190
    /* Check the channel */
191
0
    if(!channel) {
192
0
        UA_LOG_WARNING_SESSION(server->config.logging, session,
193
0
                               "Async Service Response cannot be sent. "
194
0
                               "No SecureChannel for the session.");
195
0
        return;
196
0
    }
197
198
    /* Set the request handle */
199
0
    UA_ResponseHeader *responseHeader = (UA_ResponseHeader*)
200
0
        &ar->response.callResponse.responseHeader;
201
0
    responseHeader->requestHandle = ar->requestHandle;
202
203
    /* Send the Response */
204
0
    UA_StatusCode res = sendResponse(server, channel, ar->responseToken,
205
0
                                     (UA_Response*)&ar->response, ar->responseType);
206
0
    if(res != UA_STATUSCODE_GOOD) {
207
0
        UA_LOG_WARNING_SESSION(server->config.logging, session,
208
0
                               "Async response for token %" PRIu64 " failed "
209
0
                               "with StatusCode %s", ar->responseToken,
210
0
                               UA_StatusCode_name(res));
211
0
    }
212
0
}
213
214
static void
215
0
directOpCallback(UA_Server *server, UA_AsyncOperation *op) {
216
0
    switch(op->asyncOperationType) {
217
0
    case UA_ASYNCOPERATIONTYPE_READ_DIRECT:
218
0
        op->handling.callback.method.read(server,
219
0
                                          op->handling.callback.context,
220
0
                                          &op->output.directRead);
221
0
        break;
222
0
    case UA_ASYNCOPERATIONTYPE_WRITE_DIRECT:
223
0
        op->handling.callback.method.write(server,
224
0
                                           op->handling.callback.context,
225
0
                                           op->output.directWrite);
226
0
        break;
227
0
    case UA_ASYNCOPERATIONTYPE_CALL_DIRECT:
228
0
        op->handling.callback.method.call(server,
229
0
                                          op->handling.callback.context,
230
0
                                          &op->output.directCall);
231
0
        break;
232
0
    default: UA_assert(false); break;
233
0
    }
234
0
}
235
236
/* Called from the EventLoop via a delayed callback */
237
static void
238
UA_AsyncManager_processReady(void *application /* UA_Server */,
239
17.4k
                             void *context /* UA_AsyncManager */) {
240
17.4k
    UA_Server *server = (UA_Server*)application;
241
17.4k
    UA_AsyncManager *am = (UA_AsyncManager*)context;
242
17.4k
    lockServer(server);
243
244
    /* Reset the delayed callback */
245
17.4k
    UA_atomic_store((UA_atomic(void*)*)&am->dc.callback, NULL);
246
247
    /* Process ready direct operations and free them */
248
17.4k
    UA_AsyncOperation *op = NULL, *op_tmp = NULL;
249
17.4k
    TAILQ_FOREACH_SAFE(op, &am->readyOps, pointers, op_tmp) {
250
0
        TAILQ_REMOVE(&am->readyOps, op, pointers);
251
0
        am->opsCount--;
252
0
        directOpCallback(server, op);
253
0
        UA_AsyncOperation_delete(op);
254
0
    }
255
256
    /* Send out ready responses */
257
17.4k
    UA_AsyncResponse *ar, *temp;
258
17.4k
    TAILQ_FOREACH_SAFE(ar, &am->readyResponses, pointers, temp) {
259
0
        TAILQ_REMOVE(&am->readyResponses, ar, pointers);
260
0
        sendAsyncResponse(server, ar);
261
0
        UA_AsyncResponse_delete(ar);
262
0
    }
263
264
17.4k
    unlockServer(server);
265
17.4k
}
266
267
static void
268
0
processOperationResult(UA_Server *server, UA_AsyncOperation *op) {
269
0
    UA_AsyncManager *am = &server->asyncManager;
270
0
    if(op->asyncOperationType >= UA_ASYNCOPERATIONTYPE_CALL_DIRECT) {
271
        /* Direct operation */
272
0
        TAILQ_REMOVE(&am->waitingOps, op, pointers);
273
0
        TAILQ_INSERT_TAIL(&am->readyOps, op, pointers);
274
0
    } else {
275
        /* Part of a service request */
276
0
        TAILQ_REMOVE(&am->waitingOps, op, pointers);
277
0
        am->opsCount--;
278
279
0
        UA_AsyncResponse *ar = op->handling.response;
280
0
        ar->opCountdown -= 1;
281
0
        if(ar->opCountdown > 0)
282
0
            return;
283
284
        /* Enqueue ar in the readyResponses */
285
0
        TAILQ_REMOVE(&am->waitingResponses, ar, pointers);
286
0
        TAILQ_INSERT_TAIL(&am->readyResponses, ar, pointers);
287
0
    }
288
289
    /* Trigger the main server thread to handle ready operations and responses */
290
0
    if(am->dc.callback == NULL) {
291
0
        UA_EventLoop *el = server->config.eventLoop;
292
0
        am->dc.callback = UA_AsyncManager_processReady;
293
0
        am->dc.application = server;
294
0
        am->dc.context = am;
295
0
        el->addDelayedCallback(el, &am->dc);
296
0
        el->cancel(el); /* Wake up the EventLoop if currently waiting in select() */
297
0
    }
298
0
}
299
300
/* Check if any operations have timed out */
301
static void
302
0
checkTimeouts(UA_Server *server, void *_) {
303
    /* Timeouts are not configured */
304
0
    if(server->config.asyncOperationTimeout <= 0.0)
305
0
        return;
306
307
0
    lockServer(server);
308
309
0
    UA_EventLoop *el = server->config.eventLoop;
310
0
    UA_AsyncManager *am = &server->asyncManager;
311
0
    const UA_DateTime tNow = el->dateTime_nowMonotonic(el);
312
313
    /* Loop over the waiting ops */
314
0
    UA_AsyncOperation *op = NULL, *op_tmp = NULL;
315
0
    TAILQ_FOREACH_SAFE(op, &am->waitingOps, pointers, op_tmp) {
316
        /* Check the timeout */
317
0
        if(op->asyncOperationType <= UA_ASYNCOPERATIONTYPE_WRITE_REQUEST) {
318
0
            if(tNow <= op->handling.response->timeout)
319
0
                continue;
320
0
        } else {
321
0
            if(tNow <= op->handling.callback.timeout)
322
0
                continue;
323
0
        }
324
325
0
        UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
326
0
                       "Operation was removed due to a timeout");
327
328
        /* Mark operation as timed out integrate */
329
0
        UA_AsyncOperation_cancel(server, op, UA_STATUSCODE_BADTIMEOUT);
330
0
        processOperationResult(server, op);
331
0
    }
332
333
0
    unlockServer(server);
334
0
}
335
336
void
337
17.4k
UA_AsyncManager_init(UA_AsyncManager *am, UA_Server *server) {
338
17.4k
    memset(am, 0, sizeof(UA_AsyncManager));
339
17.4k
    TAILQ_INIT(&am->waitingResponses);
340
17.4k
    TAILQ_INIT(&am->readyResponses);
341
17.4k
    TAILQ_INIT(&am->waitingOps);
342
17.4k
    TAILQ_INIT(&am->readyOps);
343
17.4k
}
344
345
538
void UA_AsyncManager_start(UA_AsyncManager *am, UA_Server *server) {
346
    /* Add a regular callback for cleanup and sending finished responses at a
347
     * 1s interval. */
348
538
    UA_StatusCode res = addRepeatedCallback(server, (UA_ServerCallback)checkTimeouts,
349
538
                    NULL, 1000.0, &am->checkTimeoutCallbackId);
350
538
    if(res != UA_STATUSCODE_GOOD) {
351
0
        UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
352
0
                    "Failed to register async timeout callback. "
353
0
                    "Async operations will not be cleaned up on timeout. StatusCode: %s",
354
0
                    UA_StatusCode_name(res));
355
0
        am->checkTimeoutCallbackId = 0;
356
0
    }
357
538
}
358
359
538
void UA_AsyncManager_stop(UA_AsyncManager *am, UA_Server *server) {
360
538
    removeCallback(server, am->checkTimeoutCallbackId);
361
538
    if(am->dc.callback) {
362
0
        UA_EventLoop *el = server->config.eventLoop;
363
0
        el->removeDelayedCallback(el, &am->dc);
364
0
    }
365
538
}
366
367
void
368
17.4k
UA_AsyncManager_clear(UA_AsyncManager *am, UA_Server *server) {
369
17.4k
    UA_LOCK_ASSERT(&server->serviceMutex);
370
371
    /* Cancel all operations. This moves all operations and responses into the
372
     * ready state. */
373
17.4k
    UA_AsyncOperation *op, *op_tmp;
374
17.4k
    TAILQ_FOREACH_SAFE(op, &am->waitingOps, pointers, op_tmp) {
375
0
        UA_AsyncOperation_cancel(server, op, UA_STATUSCODE_BADSHUTDOWN);
376
0
        processOperationResult(server, op);
377
0
    }
378
379
    /* This sends out/notifies and removes all direct operations and async requests */
380
17.4k
    UA_AsyncManager_processReady(server, am);
381
17.4k
    UA_assert(am->opsCount == 0);
382
17.4k
}
383
384
UA_UInt32
385
20
UA_AsyncManager_cancel(UA_Server *server, UA_Session *session, UA_UInt32 requestHandle) {
386
20
    UA_LOCK_ASSERT(&server->serviceMutex);
387
388
    /* Loop over all waiting operations */
389
20
    UA_UInt32 count = 0;
390
20
    UA_AsyncOperation *op, *op_tmp;
391
20
    UA_AsyncManager *am = &server->asyncManager;
392
20
    TAILQ_FOREACH_SAFE(op, &am->waitingOps, pointers, op_tmp) {
393
0
        UA_AsyncResponse *ar = op->handling.response;
394
0
        if(ar->requestHandle != requestHandle ||
395
0
           !UA_NodeId_equal(&session->sessionId, &ar->sessionId))
396
0
            continue;
397
398
0
        count++; /* Found a matching request */
399
400
        /* Set the status of the overall response */
401
0
        ar->response.callResponse.responseHeader.serviceResult =
402
0
            UA_STATUSCODE_BADREQUESTCANCELLEDBYCLIENT;
403
404
        /* Notify, set operation status and integrate */
405
0
        UA_AsyncOperation_cancel(server, op, UA_STATUSCODE_BADOPERATIONABANDONED);
406
0
        processOperationResult(server, op);
407
0
    }
408
409
20
    return count;
410
20
}
411
412
void
413
UA_AsyncManager_abandon(UA_Server *server, UA_SecureChannel *channel,
414
0
                        UA_UInt64 responseToken) {
415
0
    UA_LOCK_ASSERT(&server->serviceMutex);
416
0
    UA_AsyncManager *am = &server->asyncManager;
417
0
    UA_AsyncResponse *ar;
418
0
    TAILQ_FOREACH(ar, &am->waitingResponses, pointers) {
419
0
        if(ar->responseToken != responseToken)
420
0
            continue;
421
0
        UA_Session *session = getSessionById(server, &ar->sessionId);
422
0
        if(session && session->channel == channel)
423
0
            ar->abandoned = true;
424
0
    }
425
0
    TAILQ_FOREACH(ar, &am->readyResponses, pointers) {
426
0
        if(ar->responseToken != responseToken)
427
0
            continue;
428
0
        UA_Session *session = getSessionById(server, &ar->sessionId);
429
0
        if(session && session->channel == channel)
430
0
            ar->abandoned = true;
431
0
    }
432
0
}
433
434
static void
435
persistAsyncResponse(UA_Server *server, UA_Session *session,
436
0
                     void *response, UA_AsyncResponse *ar) {
437
0
    UA_LOCK_ASSERT(&server->serviceMutex);
438
0
    UA_AsyncManager *am = &server->asyncManager;
439
440
    /* Pending results, attach the AsyncResponse to the AsyncManager. The
441
     * transport correlation token, optional UACP RequestId and client-supplied
442
     * RequestHandle are set before processing the request. */
443
0
    ar->responseToken = am->currentResponseToken;
444
0
    ar->uacpRequestId = am->currentUacpRequestId;
445
0
    ar->requestHandle = am->currentRequestHandle;
446
0
    ar->sessionId = session->sessionId;
447
0
    ar->timeout = UA_INT64_MAX;
448
449
0
    UA_EventLoop *el = server->config.eventLoop;
450
0
    if(server->config.asyncOperationTimeout > 0.0)
451
0
        ar->timeout = el->dateTime_nowMonotonic(el) + (UA_DateTime)
452
0
            (server->config.asyncOperationTimeout * (UA_DateTime)UA_DATETIME_MSEC);
453
454
    /* Move the response content to the AsyncResponse */
455
0
    memcpy(&ar->response, response, ar->responseType->memSize);
456
0
    UA_init(response, ar->responseType);
457
458
    /* Enqueue the ar */
459
0
    TAILQ_INSERT_TAIL(&am->waitingResponses, ar, pointers);
460
0
}
461
462
static void
463
persistAsyncResponseOperation(UA_Server *server, UA_AsyncOperation *op,
464
                              UA_AsyncOperationType opType, UA_AsyncResponse *ar,
465
0
                              void *outputPtr) {
466
    /* Set up the async operation */
467
0
    op->asyncOperationType = opType;
468
0
    op->handling.response = ar;
469
0
    op->output.read = (UA_DataValue*)outputPtr;
470
471
    /* Not enough resources to store the async operation */
472
0
    UA_AsyncManager *am = &server->asyncManager;
473
0
    if(server->config.maxAsyncOperationQueueSize != 0 &&
474
0
       am->opsCount >= server->config.maxAsyncOperationQueueSize) {
475
0
        UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
476
0
                       "Cannot create async operation: Queue exceeds limit (%d).",
477
0
                       (int unsigned)server->config.maxAsyncOperationQueueSize);
478
        /* No need to call processOperationResult or UA_AsyncOperation_delete
479
         * here. The response already has the status code integrated. */
480
0
        UA_AsyncOperation_cancel(server, op, UA_STATUSCODE_BADTOOMANYOPERATIONS);
481
0
        return;
482
0
    }
483
484
    /* Enqueue the asyncop in the async manager */
485
0
    TAILQ_INSERT_TAIL(&am->waitingOps, op, pointers);
486
0
    ar->opCountdown++;
487
0
    am->opsCount++;
488
0
}
489
490
static UA_StatusCode
491
persistAsyncDirectOperation(UA_Server *server, UA_AsyncOperation *op,
492
                            UA_AsyncOperationType opType, void *context,
493
0
                            uintptr_t callback, UA_DateTime timeout) {
494
    /* Set up the async operation */
495
0
    op->asyncOperationType = opType;
496
0
    op->handling.callback.timeout = timeout;
497
0
    op->handling.callback.context = context;
498
0
    op->handling.callback.method.read = (UA_ServerAsyncReadResultCallback)callback;
499
500
    /* Not enough resources to store the async operation */
501
0
    UA_AsyncManager *am = &server->asyncManager;
502
0
    if(server->config.maxAsyncOperationQueueSize != 0 &&
503
0
       am->opsCount >= server->config.maxAsyncOperationQueueSize) {
504
0
        UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
505
0
                       "Cannot create async operation: Queue exceeds limit (%d).",
506
0
                       (int unsigned)server->config.maxAsyncOperationQueueSize);
507
0
        UA_AsyncOperation_cancel(server, op, UA_STATUSCODE_BADTOOMANYOPERATIONS);
508
0
        UA_AsyncOperation_delete(op);
509
0
        return UA_STATUSCODE_BADTOOMANYOPERATIONS;
510
0
    }
511
512
    /* Enqueue the asyncop in the async manager */
513
0
    TAILQ_INSERT_TAIL(&am->waitingOps, op, pointers);
514
0
    am->opsCount++;
515
0
    return UA_STATUSCODE_GOOD;
516
0
}
517
518
void
519
async_cancel(UA_Server *server, void *context, UA_StatusCode opstatus,
520
0
             UA_Boolean cancelSynchronous) {
521
0
    UA_AsyncManager *am = &server->asyncManager;
522
0
    UA_AsyncOperation *op = NULL, *op_tmp = NULL;
523
524
    /* Cancel operations that are still waiting for the result */
525
0
    TAILQ_FOREACH_SAFE(op, &am->waitingOps, pointers, op_tmp) {
526
0
        if(op->handling.callback.context != context)
527
0
            continue;
528
529
        /* Cancel the operation. This sets the StatusCode and calls the
530
         * asyncOperationCancelCallback. */
531
0
        UA_AsyncOperation_cancel(server, op, opstatus);
532
533
        /* Call the result-callback of the local async operation.
534
         * Right away or in the next EventLoop iteration. */
535
0
        if(cancelSynchronous) {
536
0
            TAILQ_REMOVE(&am->waitingOps, op, pointers);
537
0
            am->opsCount--;
538
0
            directOpCallback(server, op);
539
0
            UA_AsyncOperation_delete(op);
540
0
        } else {
541
0
            processOperationResult(server, op);
542
0
        }
543
0
    }
544
545
    /* All "ready" operations get processed in the next EventLoop iteration anyway */
546
0
    if(!cancelSynchronous)
547
0
        return;
548
549
    /* Process matching ready operations synchronously and delete them */
550
0
    TAILQ_FOREACH_SAFE(op, &am->readyOps, pointers, op_tmp) {
551
0
        if(op->handling.callback.context != context)
552
0
            continue;
553
0
        TAILQ_REMOVE(&am->readyOps, op, pointers);
554
0
        am->opsCount--;
555
0
        directOpCallback(server, op);
556
0
        UA_AsyncOperation_delete(op);
557
0
    }
558
0
}
559
560
void
561
UA_Server_cancelAsync(UA_Server *server, void *context, UA_StatusCode opstatus,
562
0
                      UA_Boolean synchronousResultCallback) {
563
0
    lockServer(server);
564
0
    async_cancel(server, context, opstatus, synchronousResultCallback);
565
0
    unlockServer(server);
566
0
}
567
568
/********/
569
/* Read */
570
/********/
571
572
UA_Boolean
573
271
Service_Read(UA_Server *server, UA_Session *session, const void *request_, void *response_) {
574
271
    const UA_ReadRequest *request = (const UA_ReadRequest*)request_;
575
271
    UA_ReadResponse *response = (UA_ReadResponse*)response_;
576
271
    UA_LOG_DEBUG_SESSION(server->config.logging, session, "Processing ReadRequest");
577
271
    UA_LOCK_ASSERT(&server->serviceMutex);
578
579
    /* Check if the timestampstoreturn is valid */
580
271
    if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
581
66
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID;
582
66
        return true;
583
66
    }
584
585
    /* Check if maxAge is valid */
586
205
    if(request->maxAge < 0) {
587
7
        response->responseHeader.serviceResult = UA_STATUSCODE_BADMAXAGEINVALID;
588
7
        return true;
589
7
    }
590
591
    /* Check if there are too many operations */
592
198
    if(server->config.maxNodesPerRead != 0 &&
593
0
       request->nodesToReadSize > server->config.maxNodesPerRead) {
594
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
595
0
        return true;
596
0
    }
597
598
    /* Check if there are no operations */
599
198
    if(request->nodesToReadSize == 0) {
600
13
        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
601
13
        return true;
602
13
    }
603
604
    /* Allocate the results array */
605
185
    UA_AsyncResponse *ar = NULL;
606
185
    UA_AsyncOperation *aopArray = NULL;
607
185
    response->results = (UA_DataValue*)
608
185
        allocateResultsArray(&UA_TYPES[UA_TYPES_DATAVALUE],
609
185
                             request->nodesToReadSize, &ar, &aopArray);
610
185
    if(!response->results) {
611
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
612
0
        return true;
613
0
    }
614
185
    response->resultsSize = request->nodesToReadSize;
615
616
    /* Execute the operations */
617
411
    for(size_t i = 0; i < request->nodesToReadSize; i++) {
618
226
        UA_Boolean done = Operation_Read(server, session, request->timestampsToReturn,
619
226
                                         &request->nodesToRead[i], &response->results[i]);
620
226
        if(!done)
621
0
            persistAsyncResponseOperation(server, &aopArray[i],
622
0
                                          UA_ASYNCOPERATIONTYPE_READ_REQUEST,
623
0
                                          ar, &response->results[i]);
624
226
    }
625
626
    /* If async operations are pending, persist them and signal the service is
627
     * not done */
628
185
    if(ar->opCountdown > 0) {
629
0
        ar->responseType = &UA_TYPES[UA_TYPES_READRESPONSE];
630
0
        persistAsyncResponse(server, session, response, ar);
631
0
    }
632
185
    return (ar->opCountdown == 0);
633
185
}
634
635
static UA_StatusCode
636
readOptionalNode_async(UA_Server *server, UA_Session *session,
637
                       const UA_Node *node,
638
                       const UA_ReadValueId *operation,
639
                       UA_TimestampsToReturn ttr,
640
                       UA_ServerAsyncReadResultCallback callback,
641
0
                       void *context, UA_UInt32 timeout) {
642
    /* Allocate the async operation. Do this first as we need the pointer to the
643
     * datavalue to be stable.*/
644
0
    UA_AsyncOperation *op = (UA_AsyncOperation*)UA_calloc(1, sizeof(UA_AsyncOperation));
645
0
    if(!op)
646
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
647
648
0
    UA_AsyncManager *am = &server->asyncManager;
649
0
    if(server->config.maxAsyncOperationQueueSize != 0 &&
650
0
       am->opsCount >= server->config.maxAsyncOperationQueueSize) {
651
0
        UA_free(op);
652
0
        return UA_STATUSCODE_BADTOOMANYOPERATIONS;
653
0
    }
654
655
0
    UA_DateTime timeoutDate = UA_INT64_MAX;
656
0
    if(timeout > 0) {
657
0
        UA_EventLoop *el = server->config.eventLoop;
658
0
        const UA_DateTime tNow = el->dateTime_nowMonotonic(el);
659
0
        timeoutDate = tNow + (timeout * UA_DATETIME_MSEC);
660
0
    }
661
662
    /* Call the operation */
663
0
    UA_Boolean done = node ?
664
0
        Operation_ReadWithNode(server, session, node, ttr, operation,
665
0
                               &op->output.directRead) :
666
0
        Operation_Read(server, session, ttr, operation, &op->output.directRead);
667
0
    if(!done)
668
0
        return persistAsyncDirectOperation(server, op, UA_ASYNCOPERATIONTYPE_READ_DIRECT,
669
0
                                           context, (uintptr_t)callback, timeoutDate);
670
671
0
    callback(server, context, &op->output.directRead);
672
0
    UA_DataValue_clear(&op->output.directRead);
673
0
    UA_free(op);
674
0
    return UA_STATUSCODE_GOOD;
675
0
}
676
677
UA_StatusCode
678
read_async(UA_Server *server, UA_Session *session,
679
           const UA_ReadValueId *operation, UA_TimestampsToReturn ttr,
680
           UA_ServerAsyncReadResultCallback callback,
681
0
           void *context, UA_UInt32 timeout) {
682
0
    return readOptionalNode_async(server, session, NULL, operation, ttr,
683
0
                                  callback, context, timeout);
684
0
}
685
686
UA_StatusCode
687
readWithNode_async(UA_Server *server, UA_Session *session,
688
                   const UA_Node *node, const UA_ReadValueId *operation,
689
                   UA_TimestampsToReturn ttr,
690
                   UA_ServerAsyncReadResultCallback callback,
691
0
                   void *context, UA_UInt32 timeout) {
692
0
    UA_LOCK_ASSERT(&server->serviceMutex);
693
0
    UA_assert(node != NULL);
694
0
    UA_assert(UA_NodeId_equal(&node->head.nodeId, &operation->nodeId));
695
0
    return readOptionalNode_async(server, session, node, operation, ttr,
696
0
                                  callback, context, timeout);
697
0
}
698
699
UA_StatusCode
700
UA_Server_read_async(UA_Server *server, const UA_ReadValueId *operation,
701
                     UA_TimestampsToReturn ttr, UA_ServerAsyncReadResultCallback callback,
702
0
                     void *context, UA_UInt32 timeout) {
703
0
    lockServer(server);
704
0
    UA_StatusCode res = read_async(server, &server->adminSession, operation,
705
0
                                   ttr, callback, context, timeout);
706
0
    unlockServer(server);
707
0
    return res;
708
0
}
709
710
UA_StatusCode
711
0
UA_Server_setAsyncReadResult(UA_Server *server, UA_DataValue *result) {
712
0
    lockServer(server);
713
0
    UA_AsyncManager *am = &server->asyncManager;
714
0
    UA_AsyncOperation *op = NULL;
715
0
    TAILQ_FOREACH(op, &am->waitingOps, pointers) {
716
0
        if(op->output.read == result || &op->output.directRead == result) {
717
0
            processOperationResult(server, op);
718
0
            break;
719
0
        }
720
0
    }
721
0
    unlockServer(server);
722
0
    return (op) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
723
0
}
724
725
/*********/
726
/* Write */
727
/*********/
728
729
UA_Boolean
730
Service_Write(UA_Server *server, UA_Session *session,
731
0
              const void *request_, void *response_) {
732
0
    const UA_WriteRequest *request = (const UA_WriteRequest*)request_;
733
0
    UA_WriteResponse *response = (UA_WriteResponse*)response_;
734
0
    UA_assert(session != NULL);
735
0
    UA_LOG_DEBUG_SESSION(server->config.logging, session,
736
0
                         "Processing WriteRequest");
737
0
    UA_LOCK_ASSERT(&server->serviceMutex);
738
739
0
    if(server->config.maxNodesPerWrite != 0 &&
740
0
       request->nodesToWriteSize > server->config.maxNodesPerWrite) {
741
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
742
0
        return true;
743
0
    }
744
745
0
    if(request->nodesToWriteSize == 0) {
746
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
747
0
        return true;
748
0
    }
749
750
    /* Allocate the results array */
751
0
    UA_AsyncResponse *ar = NULL;
752
0
    UA_AsyncOperation *aopArray = NULL;
753
0
    response->results = (UA_StatusCode*)
754
0
        allocateResultsArray(&UA_TYPES[UA_TYPES_STATUSCODE],
755
0
                             request->nodesToWriteSize, &ar, &aopArray);
756
0
    if(!response->results) {
757
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
758
0
        return true;
759
0
    }
760
0
    response->resultsSize = request->nodesToWriteSize;
761
762
    /* Execute the operations */
763
0
    for(size_t i = 0; i < request->nodesToWriteSize; i++) {
764
        /* Ensure a stable pointer for the writevalue. Doesn't get written to,
765
         * just used for the lookup of the async operation later on.
766
         * The original writeValue might be _clear'ed before the lookup. */
767
0
        UA_AsyncOperation *aop = &aopArray[i];
768
0
        aop->context.writeValue = request->nodesToWrite[i];
769
0
        UA_Boolean done = Operation_Write(server, session, &aop->context.writeValue,
770
0
                                          &response->results[i]);
771
0
        if(!done)
772
0
            persistAsyncResponseOperation(server, aop, UA_ASYNCOPERATIONTYPE_WRITE_REQUEST,
773
0
                                          ar, &response->results[i]);
774
0
    }
775
776
    /* If async operations are pending, persist them and signal the service is
777
     * not done */
778
0
    if(ar->opCountdown > 0) {
779
0
        ar->responseType = &UA_TYPES[UA_TYPES_WRITERESPONSE];
780
0
        persistAsyncResponse(server, session, response, ar);
781
0
    }
782
0
    return (ar->opCountdown == 0);
783
0
}
784
785
static UA_StatusCode
786
writeOptionalNode_async(UA_Server *server, UA_Session *session,
787
                        UA_Node *node, const UA_WriteValue *operation,
788
                        UA_ServerAsyncWriteResultCallback callback,
789
0
                        void *context, UA_UInt32 timeout) {
790
    /* Allocate the async operation. Do this first as we need the pointer to the
791
     * datavalue to be stable.*/
792
0
    UA_AsyncOperation *op = (UA_AsyncOperation*)UA_calloc(1, sizeof(UA_AsyncOperation));
793
0
    if(!op)
794
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
795
796
0
    UA_AsyncManager *am = &server->asyncManager;
797
0
    if(server->config.maxAsyncOperationQueueSize != 0 &&
798
0
       am->opsCount >= server->config.maxAsyncOperationQueueSize) {
799
0
        UA_free(op);
800
0
        return UA_STATUSCODE_BADTOOMANYOPERATIONS;
801
0
    }
802
803
0
    UA_DateTime timeoutDate = UA_INT64_MAX;
804
0
    if(timeout > 0) {
805
0
        UA_EventLoop *el = server->config.eventLoop;
806
0
        const UA_DateTime tNow = el->dateTime_nowMonotonic(el);
807
0
        timeoutDate = tNow + (timeout * UA_DATETIME_MSEC);
808
0
    }
809
810
    /* Call the operation */
811
0
    op->context.writeValue = *operation; /* Stable pointer */
812
0
    UA_Boolean done = node ?
813
0
        Operation_WriteWithNode(server, session, node,
814
0
                                &op->context.writeValue,
815
0
                                &op->output.directWrite) :
816
0
        Operation_Write(server, session, &op->context.writeValue,
817
0
                        &op->output.directWrite);
818
0
    if(!done)
819
0
        return persistAsyncDirectOperation(server, op, UA_ASYNCOPERATIONTYPE_WRITE_DIRECT,
820
0
                                           context, (uintptr_t)callback, timeoutDate);
821
822
    /* Done, return right away */
823
0
    callback(server, context, op->output.directWrite);
824
0
    UA_free(op);
825
0
    return UA_STATUSCODE_GOOD;
826
0
}
827
828
UA_StatusCode
829
write_async(UA_Server *server, UA_Session *session,
830
            const UA_WriteValue *operation,
831
            UA_ServerAsyncWriteResultCallback callback, void *context,
832
0
            UA_UInt32 timeout) {
833
0
    return writeOptionalNode_async(server, session, NULL, operation,
834
0
                                   callback, context, timeout);
835
0
}
836
837
UA_StatusCode
838
writeWithNode_async(UA_Server *server, UA_Session *session,
839
                    UA_Node *node, const UA_WriteValue *operation,
840
                    UA_ServerAsyncWriteResultCallback callback,
841
0
                    void *context, UA_UInt32 timeout) {
842
0
    UA_LOCK_ASSERT(&server->serviceMutex);
843
0
    UA_assert(node != NULL);
844
0
    UA_assert(UA_NodeId_equal(&node->head.nodeId, &operation->nodeId));
845
0
    return writeOptionalNode_async(server, session, node, operation,
846
0
                                   callback, context, timeout);
847
0
}
848
849
UA_StatusCode
850
UA_Server_write_async(UA_Server *server, const UA_WriteValue *operation,
851
                      UA_ServerAsyncWriteResultCallback callback,
852
0
                      void *context, UA_UInt32 timeout) {
853
0
    lockServer(server);
854
0
    UA_StatusCode res = write_async(server, &server->adminSession, operation,
855
0
                                    callback, context, timeout);
856
0
    unlockServer(server);
857
0
    return res;
858
0
}
859
860
UA_StatusCode
861
UA_Server_setAsyncWriteResult(UA_Server *server,
862
                              const UA_DataValue *value,
863
0
                              UA_StatusCode result) {
864
0
    lockServer(server);
865
0
    UA_AsyncManager *am = &server->asyncManager;
866
0
    UA_AsyncOperation *op = NULL;
867
0
    TAILQ_FOREACH(op, &am->waitingOps, pointers) {
868
0
        if(&op->context.writeValue.value == value) {
869
0
            if(op->asyncOperationType == UA_ASYNCOPERATIONTYPE_WRITE_REQUEST)
870
0
                *op->output.write = result;
871
0
            else
872
0
                op->output.directWrite = result;
873
0
            processOperationResult(server, op);
874
0
            break;
875
0
        }
876
0
    }
877
0
    unlockServer(server);
878
0
    return (op) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
879
0
}
880
881
/********/
882
/* Call */
883
/********/
884
885
#ifdef UA_ENABLE_METHODCALLS
886
UA_Boolean
887
Service_Call(UA_Server *server, UA_Session *session,
888
31
             const void *request_, void *response_) {
889
31
    const UA_CallRequest *request = (const UA_CallRequest*)request_;
890
31
    UA_CallResponse *response = (UA_CallResponse*)response_;
891
31
    UA_LOG_DEBUG_SESSION(server->config.logging, session, "Processing CallRequest");
892
31
    UA_LOCK_ASSERT(&server->serviceMutex);
893
894
31
    if(server->config.maxNodesPerMethodCall != 0 &&
895
0
        request->methodsToCallSize > server->config.maxNodesPerMethodCall) {
896
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADTOOMANYOPERATIONS;
897
0
        return true;
898
0
    }
899
900
31
    if(request->methodsToCallSize == 0) {
901
10
        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
902
10
        return true;
903
10
    }
904
905
    /* Allocate the results array */
906
21
    UA_AsyncResponse *ar = NULL;
907
21
    UA_AsyncOperation *aopArray = NULL;
908
21
    response->results = (UA_CallMethodResult*)
909
21
        allocateResultsArray(&UA_TYPES[UA_TYPES_CALLMETHODRESULT],
910
21
                             request->methodsToCallSize, &ar, &aopArray);
911
21
    if(!response->results) {
912
0
        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
913
0
        return true;
914
0
    }
915
21
    response->resultsSize = request->methodsToCallSize;
916
917
    /* Execute the operations */
918
71
    for(size_t i = 0; i < request->methodsToCallSize; i++) {
919
50
        UA_Boolean done = Operation_CallMethod(server, session, &request->methodsToCall[i],
920
50
                                               &response->results[i]);
921
50
        if(!done)
922
0
            persistAsyncResponseOperation(server, &aopArray[i],
923
0
                                          UA_ASYNCOPERATIONTYPE_CALL_REQUEST,
924
0
                                          ar, &response->results[i]);
925
50
    }
926
927
    /* If async operations are pending, persist them and signal the service is
928
     * not done */
929
21
    if(ar->opCountdown > 0) {
930
0
        ar->responseType = &UA_TYPES[UA_TYPES_CALLRESPONSE];
931
0
        persistAsyncResponse(server, session, response, ar);
932
0
    }
933
21
    return (ar->opCountdown == 0);
934
21
}
935
936
UA_StatusCode
937
call_async(UA_Server *server, UA_Session *session, const UA_CallMethodRequest *operation,
938
           UA_ServerAsyncMethodResultCallback callback, void *context,
939
0
           UA_UInt32 timeout) {
940
    /* Allocate the async operation. Do this first as we need the pointer to the
941
     * datavalue to be stable.*/
942
0
    UA_AsyncOperation *op = (UA_AsyncOperation*)UA_calloc(1, sizeof(UA_AsyncOperation));
943
0
    if(!op)
944
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
945
946
0
    UA_AsyncManager *am = &server->asyncManager;
947
0
    if(server->config.maxAsyncOperationQueueSize != 0 &&
948
0
       am->opsCount >= server->config.maxAsyncOperationQueueSize) {
949
0
        UA_free(op);
950
0
        return UA_STATUSCODE_BADTOOMANYOPERATIONS;
951
0
    }
952
953
0
    UA_DateTime timeoutDate = UA_INT64_MAX;
954
0
    if(timeout > 0) {
955
0
        UA_EventLoop *el = server->config.eventLoop;
956
0
        const UA_DateTime tNow = el->dateTime_nowMonotonic(el);
957
0
        timeoutDate = tNow + (timeout * UA_DATETIME_MSEC);
958
0
    }
959
960
    /* Call the operation */
961
0
    UA_Boolean done = Operation_CallMethod(server, session, operation,
962
0
                                           &op->output.directCall);
963
0
    if(!done)
964
0
        return persistAsyncDirectOperation(server, op, UA_ASYNCOPERATIONTYPE_CALL_DIRECT,
965
0
                                           context, (uintptr_t)callback, timeoutDate);
966
967
    /* Done, return right away */
968
0
    callback(server, context, &op->output.directCall);
969
0
    UA_CallMethodResult_clear(&op->output.directCall);
970
0
    UA_free(op);
971
0
    return UA_STATUSCODE_GOOD;
972
0
}
973
974
UA_StatusCode
975
UA_Server_call_async(UA_Server *server, const UA_CallMethodRequest *operation,
976
                     UA_ServerAsyncMethodResultCallback callback,
977
0
                     void *context, UA_UInt32 timeout) {
978
0
    lockServer(server);
979
0
    UA_StatusCode res =
980
0
        call_async(server, &server->adminSession, operation, callback, context, timeout);
981
0
    unlockServer(server);
982
0
    return res;
983
0
}
984
985
UA_StatusCode
986
UA_Server_setAsyncCallMethodResult(UA_Server *server, UA_Variant *output,
987
0
                                   UA_StatusCode result) {
988
0
    lockServer(server);
989
0
    UA_AsyncManager *am = &server->asyncManager;
990
0
    UA_AsyncOperation *op = NULL;
991
0
    TAILQ_FOREACH(op, &am->waitingOps, pointers) {
992
0
        if(op->asyncOperationType == UA_ASYNCOPERATIONTYPE_CALL_REQUEST) {
993
0
            if(op->output.call->outputArguments == output) {
994
0
                op->output.call->statusCode = result;
995
0
                processOperationResult(server, op);
996
0
                break;
997
0
            }
998
0
        } else if(op->asyncOperationType == UA_ASYNCOPERATIONTYPE_CALL_DIRECT) {
999
0
            if(op->output.directCall.outputArguments == output) {
1000
0
                op->output.directCall.statusCode = result;
1001
0
                processOperationResult(server, op);
1002
0
                break;
1003
0
            }
1004
0
        }
1005
0
    }
1006
0
    unlockServer(server);
1007
0
    return (op) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
1008
0
}
1009
#endif