Coverage Report

Created: 2026-05-16 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541_15/src/server/ua_services_method.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 2015 (c) Chris Iatrou
6
 *    Copyright 2015-2017 (c) Florian Palm
7
 *    Copyright 2015-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
8
 *    Copyright 2015-2016 (c) Sten Grüner
9
 *    Copyright 2015 (c) Oleksiy Vasylyev
10
 *    Copyright 2016 (c) LEvertz
11
 *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
12
 *    Copyright 2017 (c) Julian Grothoff
13
 *    Copyright 2020 (c) Hilscher Gesellschaft für Systemautomation mbH (Author: Martin Lang)
14
 *    Copyright (c) 2025 Pilz GmbH & Co. KG, Author: Marcel Patzlaff
15
 */
16
17
#include "ua_services.h"
18
#include "ua_server_internal.h"
19
20
#ifdef UA_ENABLE_METHODCALLS /* conditional compilation */
21
22
0
#define UA_MAX_METHOD_ARGUMENTS 64
23
24
struct GetArgumentsNodeContext {
25
    UA_Server *server;
26
    UA_String withBrowseName;
27
};
28
29
static void *
30
0
getArgumentsNodeCallback(void *context, UA_ReferenceTarget *t) {
31
0
    struct GetArgumentsNodeContext *ctx = (struct GetArgumentsNodeContext*)context;
32
0
    const UA_Node *refTarget =
33
0
        UA_NODESTORE_GETFROMREF_SELECTIVE(ctx->server, t->targetId,
34
0
                                          UA_NODEATTRIBUTESMASK_NODECLASS |
35
0
                                          UA_NODEATTRIBUTESMASK_VALUE,
36
0
                                          UA_REFERENCETYPESET_NONE,
37
0
                                          UA_BROWSEDIRECTION_INVALID);
38
0
    if(!refTarget)
39
0
        return NULL;
40
0
    if(refTarget->head.nodeClass == UA_NODECLASS_VARIABLE &&
41
0
       refTarget->head.browseName.namespaceIndex == 0 &&
42
0
       UA_String_equal(&ctx->withBrowseName, &refTarget->head.browseName.name)) {
43
0
        return (void*)(uintptr_t)&refTarget->variableNode;
44
0
    }
45
0
    UA_NODESTORE_RELEASE(ctx->server, refTarget);
46
0
    return NULL;
47
0
}
48
49
static const UA_VariableNode *
50
getArgumentsVariableNode(UA_Server *server, const UA_NodeHead *head,
51
0
                         UA_String withBrowseName) {
52
0
    for(size_t i = 0; i < head->referencesSize; ++i) {
53
0
        UA_NodeReferenceKind *rk = &head->references[i];
54
0
        if(rk->isInverse)
55
0
            continue;
56
0
        if(rk->referenceTypeIndex != UA_REFERENCETYPEINDEX_HASPROPERTY)
57
0
            continue;
58
0
        struct GetArgumentsNodeContext ctx;
59
0
        ctx.server = server;
60
0
        ctx.withBrowseName = withBrowseName;
61
0
        return (const UA_VariableNode*)
62
0
            UA_NodeReferenceKind_iterate(rk, getArgumentsNodeCallback, &ctx);
63
0
    }
64
0
    return NULL;
65
0
}
66
67
/* inputArgumentResults has the length request->inputArgumentsSize */
68
static UA_StatusCode
69
checkAdjustArguments(UA_Server *server, UA_Session *session,
70
                     const UA_VariableNode *argRequirements, size_t argsSize,
71
0
                     UA_Variant *args, UA_StatusCode *inputArgumentResults) {
72
    /* Verify that we have a Variant containing UA_Argument (scalar or array) in
73
     * the "InputArguments" node */
74
0
    if(argRequirements->valueSourceType != UA_VALUESOURCETYPE_INTERNAL)
75
0
        return UA_STATUSCODE_BADINTERNALERROR;
76
0
    if(!argRequirements->valueSource.internal.value.hasValue)
77
0
        return UA_STATUSCODE_BADINTERNALERROR;
78
79
0
    const UA_Variant *argVal = &argRequirements->valueSource.internal.value.value;
80
0
    if(argVal->type != &UA_TYPES[UA_TYPES_ARGUMENT])
81
0
        return UA_STATUSCODE_BADINTERNALERROR;
82
83
    /* Verify the number of arguments. A scalar argument value is interpreted as
84
     * an array of length 1. */
85
0
    size_t argReqsSize = argVal->arrayLength;
86
0
    if(UA_Variant_isScalar(argVal))
87
0
        argReqsSize = 1;
88
0
    if(argReqsSize > argsSize)
89
0
        return UA_STATUSCODE_BADARGUMENTSMISSING;
90
0
    if(argReqsSize < argsSize)
91
0
        return UA_STATUSCODE_BADTOOMANYARGUMENTS;
92
93
    /* Type-check every argument against the definition */
94
0
    UA_StatusCode retval = UA_STATUSCODE_GOOD;
95
0
    UA_Argument *argReqs = (UA_Argument*)argVal->data;
96
0
    const char *reason;
97
0
    for(size_t i = 0; i < argReqsSize; ++i) {
98
        /* Incompatible value. Try to correct the type if possible. */
99
0
        adjustValueType(server, &args[i], &argReqs[i].dataType);
100
101
        /* Check */
102
0
        if(!compatibleValue(server, session, &argReqs[i].dataType, argReqs[i].valueRank,
103
0
                            argReqs[i].arrayDimensionsSize, argReqs[i].arrayDimensions,
104
0
                            &args[i], NULL, &reason)) {
105
0
            inputArgumentResults[i] = UA_STATUSCODE_BADTYPEMISMATCH;
106
0
            retval = UA_STATUSCODE_BADINVALIDARGUMENT;
107
0
        }
108
0
    }
109
0
    return retval;
110
0
}
111
112
static UA_StatusCode
113
checkMethodInTypeHierarchy(UA_Server *server, UA_Session *session,
114
                           const UA_NodeId *contextId, const UA_NodeId *methodId,
115
0
                           const UA_QualifiedName *methodBrowseName) {
116
    /* Get the type hierarchy "upwards" from the context */
117
0
    UA_NodeId *typeHierarchy;
118
0
    size_t typeHierarchySize;
119
0
    UA_StatusCode res =
120
0
        getTypeAndInterfaceHierarchy(server, contextId, false,
121
0
                                     &typeHierarchy, &typeHierarchySize);
122
0
    if(res != UA_STATUSCODE_GOOD)
123
0
        return res;
124
125
    /* Check if the method is in the type hierarchy */
126
0
    for(size_t i = 0; i < typeHierarchySize; i++) {
127
0
        UA_NodeId contained;
128
0
        res = findChildByBrowsename(server, session, typeHierarchy[i],
129
0
                                    UA_NODECLASS_METHOD,
130
0
                                    UA_REFERENCETYPEINDEX_HASCOMPONENT,
131
0
                                    UA_NS0ID(HASCOMPONENT),
132
0
                                    methodBrowseName, &contained);
133
0
        if(res == UA_STATUSCODE_BADNOTFOUND)
134
0
            continue;
135
0
        if(res != UA_STATUSCODE_GOOD)
136
0
            break;
137
0
        UA_Boolean found = UA_NodeId_equal(&contained, methodId);
138
0
        UA_NodeId_clear(&contained);
139
0
        res = (found) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
140
0
        if(found)
141
0
            break;
142
0
    }
143
144
    /* Clean up and return */
145
0
    UA_Array_delete(typeHierarchy, typeHierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
146
0
    return res;
147
0
}
148
149
static UA_StatusCode
150
callWithResolvedMethodAndObject(UA_Server *server, UA_Session *session,
151
                                const UA_CallMethodRequest *request,
152
                                UA_CallMethodResult *result,
153
                                const UA_MethodNode *resolvedMethod,
154
0
                                const UA_Node *callContext) {
155
    /* Is there a method to execute? */
156
0
    if(!resolvedMethod->method)
157
0
        return UA_STATUSCODE_BADINTERNALERROR;
158
159
    /* Verify access rights */
160
0
    UA_Boolean executable = resolvedMethod->executable;
161
0
    if(!executable)
162
0
        return UA_STATUSCODE_BADNOTEXECUTABLE;
163
164
0
    if(session != &server->adminSession) {
165
0
        executable = server->config.accessControl.
166
0
            getUserExecutableOnObject(server, &server->config.accessControl,
167
0
                                      &session->sessionId, session->context,
168
0
                                      &resolvedMethod->head.nodeId,
169
0
                                      resolvedMethod->head.context,
170
0
                                      &request->objectId,
171
0
                                      callContext->head.context);
172
0
    }
173
0
    if(!executable)
174
0
        return UA_STATUSCODE_BADUSERACCESSDENIED;
175
176
    /* The input arguments are const and not changed. We move the input
177
     * arguments to a secondary array that is mutable. This is used for small
178
     * adjustments on the type level during the type checking. But it has to be
179
     * ensured that the original array can still by _clear'ed after the methods
180
     * call. */
181
0
    if(request->inputArgumentsSize > UA_MAX_METHOD_ARGUMENTS)
182
0
        return UA_STATUSCODE_BADTOOMANYARGUMENTS;
183
184
0
    UA_Variant mutableInputArgs[UA_MAX_METHOD_ARGUMENTS];
185
0
    if(request->inputArgumentsSize > 0)
186
0
        memcpy(mutableInputArgs, request->inputArguments,
187
0
               sizeof(UA_Variant) * request->inputArgumentsSize);
188
189
    /* Allocate the inputArgumentResults array */
190
0
    result->inputArgumentResults = (UA_StatusCode*)
191
0
        UA_Array_new(request->inputArgumentsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
192
0
    if(!result->inputArgumentResults)
193
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
194
0
    result->inputArgumentResultsSize = request->inputArgumentsSize;
195
196
    /* Type-check the input arguments */
197
0
    UA_StatusCode res = UA_STATUSCODE_GOOD;
198
0
    const UA_VariableNode *inputArguments =
199
0
        getArgumentsVariableNode(server, &resolvedMethod->head,
200
0
                                 UA_STRING("InputArguments"));
201
0
    if(inputArguments) {
202
0
        res = checkAdjustArguments(server, session,
203
0
                                   inputArguments, request->inputArgumentsSize,
204
0
                                   mutableInputArgs, result->inputArgumentResults);
205
0
        UA_NODESTORE_RELEASE(server, (const UA_Node*)inputArguments);
206
0
    } else if(request->inputArgumentsSize > 0) {
207
0
        res = UA_STATUSCODE_BADTOOMANYARGUMENTS;
208
0
    }
209
210
    /* Return inputArgumentResults only for BADINVALIDARGUMENT */
211
0
    if(res != UA_STATUSCODE_BADINVALIDARGUMENT) {
212
0
        UA_Array_delete(result->inputArgumentResults,
213
0
                        result->inputArgumentResultsSize,
214
0
                        &UA_TYPES[UA_TYPES_STATUSCODE]);
215
0
        result->inputArgumentResults = NULL;
216
0
        result->inputArgumentResultsSize = 0;
217
0
    }
218
219
    /* Error during type-checking? */
220
0
    if(res != UA_STATUSCODE_GOOD)
221
0
        return res;
222
223
    /* Get the output arguments node */
224
0
    const UA_VariableNode *outputArguments =
225
0
        getArgumentsVariableNode(server, &resolvedMethod->head,
226
0
                                 UA_STRING("OutputArguments"));
227
228
    /* Allocate the output arguments array. Always allocate memory, hence the
229
     * +1, even if the length is zero. Because we need a unique outputArguments
230
     * pointer as the key for async operations. The memory gets deleted in
231
     * UA_Array_delete even if the outputArgumentsSize is zero. */
232
0
    size_t outputArgsSize = 0;
233
0
    if(outputArguments)
234
0
        outputArgsSize = outputArguments->valueSource.internal.value.value.arrayLength;
235
0
    result->outputArguments = (UA_Variant*)
236
0
        UA_Array_new(outputArgsSize+1, &UA_TYPES[UA_TYPES_VARIANT]);
237
0
    if(!result->outputArguments)
238
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
239
0
    result->outputArgumentsSize = outputArgsSize;
240
241
    /* Release the output arguments node */
242
0
    UA_NODESTORE_RELEASE(server, (const UA_Node*)outputArguments);
243
244
    /* Call the method. If this is an async method, unlock the server lock for
245
     * the duration of the (long-running) call. */
246
0
    return resolvedMethod->method(server, &session->sessionId, session->context,
247
0
                                  &resolvedMethod->head.nodeId,
248
0
                                  resolvedMethod->head.context,
249
0
                                  &callContext->head.nodeId, callContext->head.context,
250
0
                                  request->inputArgumentsSize, mutableInputArgs,
251
0
                                  result->outputArgumentsSize, result->outputArguments);
252
253
    /* TODO: Verify Output matches the argument definition */
254
0
}
255
256
static UA_StatusCode
257
callWithMethodAndObject(UA_Server *server, UA_Session *session,
258
                        const UA_CallMethodRequest *request,
259
                        UA_CallMethodResult *result,
260
                        const UA_Node *callMethod,
261
0
                        const UA_Node *callObject) {
262
0
    UA_LOCK_ASSERT(&server->serviceMutex);
263
264
    /* Verify the context object's NodeClass */
265
0
    if(callObject->head.nodeClass != UA_NODECLASS_OBJECT &&
266
0
       callObject->head.nodeClass != UA_NODECLASS_OBJECTTYPE)
267
0
        return UA_STATUSCODE_BADNODECLASSINVALID;
268
269
    /* Verify the method's NodeClass */
270
0
    if(callMethod->head.nodeClass != UA_NODECLASS_METHOD)
271
0
        return UA_STATUSCODE_BADNODECLASSINVALID;
272
273
    /* For object types the given method must be directly referenced!
274
     * OPC UA 1.05.06 will state in Part 4 - Call Service Parameters
275
     * the following for parameter "objectId":
276
     * "In case of an ObjectType the ObjectType shall be the source of a
277
     *  HasComponent Reference (or subtype of HasComponent Reference) to the
278
     *  Method specified in methodId" */
279
280
    /* Find the matching Method of the same BrowseName referenced directly by
281
     * the calling object */
282
0
    UA_NodeId resolvedMethodId;
283
0
    UA_StatusCode res =
284
0
        findChildByBrowsename(server, session, callObject->head.nodeId,
285
0
                              UA_NODECLASS_METHOD, UA_REFERENCETYPEINDEX_HASCOMPONENT,
286
0
                              UA_NS0ID(HASCOMPONENT), &callMethod->head.browseName,
287
0
                              &resolvedMethodId);
288
0
    if(res != UA_STATUSCODE_GOOD)
289
0
        return (res == UA_STATUSCODE_BADNOTFOUND) ?
290
0
            UA_STATUSCODE_BADMETHODINVALID : res;
291
292
    /* The resolved methodId is different */
293
0
    const UA_MethodNode *resolvedMethod = (const UA_MethodNode*)callMethod;
294
0
    if(!UA_NodeId_equal(&resolvedMethodId, &callMethod->head.nodeId)) {
295
        /* Check whether the original MethodId comes from an ObjectType that is
296
         * upwards in the type hierarchy. Downwards in the type hierarchy (or
297
         * even completely unrelated) is not allowed. */
298
0
        res = checkMethodInTypeHierarchy(server, session,
299
0
                                         &callObject->head.nodeId,
300
0
                                         &callMethod->head.nodeId,
301
0
                                         &callMethod->head.browseName);
302
0
        if(res != UA_STATUSCODE_GOOD) {
303
0
            if(res == UA_STATUSCODE_BADNOTFOUND)
304
0
                res = UA_STATUSCODE_BADMETHODINVALID;
305
0
            goto errout;
306
0
        }
307
308
        /* Get the resolved method node */
309
0
        resolvedMethod = (const UA_MethodNode*)
310
0
            UA_NODESTORE_GET_SELECTIVE(server, &resolvedMethodId,
311
0
                                       UA_NODEATTRIBUTESMASK_NODECLASS |
312
0
                                       UA_NODEATTRIBUTESMASK_EXECUTABLE,
313
0
                                       UA_REFERENCETYPESET_NONE,
314
0
                                       UA_BROWSEDIRECTION_INVALID);
315
0
        if(!resolvedMethod) {
316
0
            res = UA_STATUSCODE_BADINTERNALERROR;
317
0
            goto errout;
318
0
        }
319
0
    }
320
321
    /* Run the main call logic */
322
0
    res = callWithResolvedMethodAndObject(server, session, request, result,
323
0
                                          resolvedMethod, callObject);
324
325
0
 errout:
326
    /* Release the resolved method node if it is different */
327
0
    if(resolvedMethod && (const UA_Node*)resolvedMethod != callMethod)
328
0
        UA_NODESTORE_RELEASE(server, (const UA_Node*)resolvedMethod);
329
0
    UA_NodeId_clear(&resolvedMethodId);
330
0
    return res;
331
0
}
332
333
UA_Boolean
334
Operation_CallMethod(UA_Server *server, UA_Session *session,
335
                     const UA_CallMethodRequest *request,
336
0
                     UA_CallMethodResult *result) {
337
0
    UA_LOCK_ASSERT(&server->serviceMutex);
338
339
    /* Get the method node. We only need the nodeClass and browseName
340
     * attribute. */
341
0
    const UA_Node *method =
342
0
        UA_NODESTORE_GET_SELECTIVE(server, &request->methodId,
343
0
                                   UA_NODEATTRIBUTESMASK_NODECLASS |
344
0
                                   UA_NODEATTRIBUTESMASK_BROWSENAME,
345
0
                                   UA_REFERENCETYPESET_NONE,
346
0
                                   UA_BROWSEDIRECTION_INVALID);
347
0
    if(!method) {
348
0
        result->statusCode = UA_STATUSCODE_BADMETHODINVALID;
349
0
        return true;
350
0
    }
351
352
    /* Get the object node. We only need the NodeClass attribute. But take all
353
     * references for now.
354
     *
355
     * TODO: Which references do we need actually? */
356
0
    const UA_Node *object =
357
0
        UA_NODESTORE_GET_SELECTIVE(server, &request->objectId,
358
0
                                   UA_NODEATTRIBUTESMASK_NODECLASS,
359
0
                                   UA_REFERENCETYPESET_ALL,
360
0
                                   UA_BROWSEDIRECTION_BOTH);
361
0
    if(!object) {
362
0
        result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
363
0
        UA_NODESTORE_RELEASE(server, method);
364
0
        return true;
365
0
    }
366
367
    /* Continue with method and object (which can be an ObjectType) as
368
     * context */
369
0
    result->statusCode =
370
0
        callWithMethodAndObject(server, session, request,
371
0
                                result, method, object);
372
373
    /* Release the method and object node */
374
0
    UA_NODESTORE_RELEASE(server, method);
375
0
    UA_NODESTORE_RELEASE(server, object);
376
377
0
    return (result->statusCode != UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY);
378
0
}
379
380
UA_CallMethodResult
381
0
UA_Server_call(UA_Server *server, const UA_CallMethodRequest *request) {
382
0
    UA_CallMethodResult result;
383
0
    UA_CallMethodResult_init(&result);
384
0
    lockServer(server);
385
0
    Operation_CallMethod(server, &server->adminSession, request, &result);
386
    /* Cancel asynchronous responses right away */
387
0
    if(result.statusCode == UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY) {
388
0
        if(server->config.asyncOperationCancelCallback)
389
0
            server->config.asyncOperationCancelCallback(server, result.outputArguments);
390
0
        result.statusCode = UA_STATUSCODE_BADWAITINGFORRESPONSE;
391
0
    }
392
0
    unlockServer(server);
393
0
    return result;
394
0
}
395
396
#endif /* UA_ENABLE_METHODCALLS */