#include "ua_services.h"
#include "ua_server_internal.h"
#ifdef UA_ENABLE_METHODCALLS
#define UA_MAX_METHOD_ARGUMENTS 64
struct GetArgumentsNodeContext {
UA_Server *server;
UA_String withBrowseName;
};
static void *
getArgumentsNodeCallback(void *context, UA_ReferenceTarget *t) {
struct GetArgumentsNodeContext *ctx = (struct GetArgumentsNodeContext*)context;
const UA_Node *refTarget =
UA_NODESTORE_GETFROMREF_SELECTIVE(ctx->server, t->targetId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_VALUE,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!refTarget)
return NULL;
if(refTarget->head.nodeClass == UA_NODECLASS_VARIABLE &&
refTarget->head.browseName.namespaceIndex == 0 &&
UA_String_equal(&ctx->withBrowseName, &refTarget->head.browseName.name)) {
return (void*)(uintptr_t)&refTarget->variableNode;
}
UA_NODESTORE_RELEASE(ctx->server, refTarget);
return NULL;
}
static const UA_VariableNode *
getArgumentsVariableNode(UA_Server *server, const UA_NodeHead *head,
UA_String withBrowseName) {
for(size_t i = 0; i < head->referencesSize; ++i) {
UA_NodeReferenceKind *rk = &head->references[i];
if(rk->isInverse)
continue;
if(rk->referenceTypeIndex != UA_REFERENCETYPEINDEX_HASPROPERTY)
continue;
struct GetArgumentsNodeContext ctx;
ctx.server = server;
ctx.withBrowseName = withBrowseName;
return (const UA_VariableNode*)
UA_NodeReferenceKind_iterate(rk, getArgumentsNodeCallback, &ctx);
}
return NULL;
}
static UA_StatusCode
checkAdjustArguments(UA_Server *server, UA_Session *session,
const UA_VariableNode *argRequirements, size_t argsSize,
UA_Variant *args, UA_StatusCode *inputArgumentResults) {
if(argRequirements->valueSourceType != UA_VALUESOURCETYPE_INTERNAL)
return UA_STATUSCODE_BADINTERNALERROR;
if(!argRequirements->valueSource.internal.value.hasValue)
return UA_STATUSCODE_BADINTERNALERROR;
const UA_Variant *argVal = &argRequirements->valueSource.internal.value.value;
if(argVal->type != &UA_TYPES[UA_TYPES_ARGUMENT])
return UA_STATUSCODE_BADINTERNALERROR;
size_t argReqsSize = argVal->arrayLength;
if(UA_Variant_isScalar(argVal))
argReqsSize = 1;
if(argReqsSize > argsSize)
return UA_STATUSCODE_BADARGUMENTSMISSING;
if(argReqsSize < argsSize)
return UA_STATUSCODE_BADTOOMANYARGUMENTS;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Argument *argReqs = (UA_Argument*)argVal->data;
const char *reason;
for(size_t i = 0; i < argReqsSize; ++i) {
adjustValueType(server, &args[i], &argReqs[i].dataType);
if(!compatibleValue(server, session, &argReqs[i].dataType, argReqs[i].valueRank,
argReqs[i].arrayDimensionsSize, argReqs[i].arrayDimensions,
&args[i], NULL, &reason)) {
inputArgumentResults[i] = UA_STATUSCODE_BADTYPEMISMATCH;
retval = UA_STATUSCODE_BADINVALIDARGUMENT;
}
}
return retval;
}
static UA_StatusCode
checkMethodInTypeHierarchy(UA_Server *server, UA_Session *session,
const UA_NodeId *contextId, const UA_NodeId *methodId,
const UA_QualifiedName *methodBrowseName) {
UA_NodeId *typeHierarchy;
size_t typeHierarchySize;
UA_StatusCode res =
getTypeAndInterfaceHierarchy(server, contextId, false,
&typeHierarchy, &typeHierarchySize);
if(res != UA_STATUSCODE_GOOD)
return res;
for(size_t i = 0; i < typeHierarchySize; i++) {
UA_NodeId contained;
res = findChildByBrowsename(server, session, typeHierarchy[i],
UA_NODECLASS_METHOD,
UA_REFERENCETYPEINDEX_HASCOMPONENT,
UA_NS0ID(HASCOMPONENT),
methodBrowseName, &contained);
if(res == UA_STATUSCODE_BADNOTFOUND)
continue;
if(res != UA_STATUSCODE_GOOD)
break;
UA_Boolean found = UA_NodeId_equal(&contained, methodId);
UA_NodeId_clear(&contained);
res = (found) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOTFOUND;
if(found)
break;
}
UA_Array_delete(typeHierarchy, typeHierarchySize, &UA_TYPES[UA_TYPES_NODEID]);
return res;
}
static UA_StatusCode
callWithResolvedMethodAndObject(UA_Server *server, UA_Session *session,
const UA_CallMethodRequest *request,
UA_CallMethodResult *result,
const UA_MethodNode *resolvedMethod,
const UA_Node *callContext) {
if(!resolvedMethod->method)
return UA_STATUSCODE_BADINTERNALERROR;
UA_Boolean executable = resolvedMethod->executable;
if(!executable)
return UA_STATUSCODE_BADNOTEXECUTABLE;
if(session != &server->adminSession) {
executable = server->config.accessControl.
getUserExecutableOnObject(server, &server->config.accessControl,
&session->sessionId, session->context,
&resolvedMethod->head.nodeId,
resolvedMethod->head.context,
&request->objectId,
callContext->head.context);
}
if(!executable)
return UA_STATUSCODE_BADUSERACCESSDENIED;
if(request->inputArgumentsSize > UA_MAX_METHOD_ARGUMENTS)
return UA_STATUSCODE_BADTOOMANYARGUMENTS;
UA_Variant mutableInputArgs[UA_MAX_METHOD_ARGUMENTS];
if(request->inputArgumentsSize > 0)
memcpy(mutableInputArgs, request->inputArguments,
sizeof(UA_Variant) * request->inputArgumentsSize);
result->inputArgumentResults = (UA_StatusCode*)
UA_Array_new(request->inputArgumentsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
if(!result->inputArgumentResults)
return UA_STATUSCODE_BADOUTOFMEMORY;
result->inputArgumentResultsSize = request->inputArgumentsSize;
UA_StatusCode res = UA_STATUSCODE_GOOD;
const UA_VariableNode *inputArguments =
getArgumentsVariableNode(server, &resolvedMethod->head,
UA_STRING("InputArguments"));
if(inputArguments) {
res = checkAdjustArguments(server, session,
inputArguments, request->inputArgumentsSize,
mutableInputArgs, result->inputArgumentResults);
UA_NODESTORE_RELEASE(server, (const UA_Node*)inputArguments);
} else if(request->inputArgumentsSize > 0) {
res = UA_STATUSCODE_BADTOOMANYARGUMENTS;
}
if(res != UA_STATUSCODE_BADINVALIDARGUMENT) {
UA_Array_delete(result->inputArgumentResults,
result->inputArgumentResultsSize,
&UA_TYPES[UA_TYPES_STATUSCODE]);
result->inputArgumentResults = NULL;
result->inputArgumentResultsSize = 0;
}
if(res != UA_STATUSCODE_GOOD)
return res;
const UA_VariableNode *outputArguments =
getArgumentsVariableNode(server, &resolvedMethod->head,
UA_STRING("OutputArguments"));
size_t outputArgsSize = 0;
if(outputArguments)
outputArgsSize = outputArguments->valueSource.internal.value.value.arrayLength;
result->outputArguments = (UA_Variant*)
UA_Array_new(outputArgsSize+1, &UA_TYPES[UA_TYPES_VARIANT]);
if(!result->outputArguments)
return UA_STATUSCODE_BADOUTOFMEMORY;
result->outputArgumentsSize = outputArgsSize;
UA_NODESTORE_RELEASE(server, (const UA_Node*)outputArguments);
return resolvedMethod->method(server, &session->sessionId, session->context,
&resolvedMethod->head.nodeId,
resolvedMethod->head.context,
&callContext->head.nodeId, callContext->head.context,
request->inputArgumentsSize, mutableInputArgs,
result->outputArgumentsSize, result->outputArguments);
}
static UA_StatusCode
callWithMethodAndObject(UA_Server *server, UA_Session *session,
const UA_CallMethodRequest *request,
UA_CallMethodResult *result,
const UA_Node *callMethod,
const UA_Node *callObject) {
UA_LOCK_ASSERT(&server->serviceMutex);
if(callObject->head.nodeClass != UA_NODECLASS_OBJECT &&
callObject->head.nodeClass != UA_NODECLASS_OBJECTTYPE)
return UA_STATUSCODE_BADNODECLASSINVALID;
if(callMethod->head.nodeClass != UA_NODECLASS_METHOD)
return UA_STATUSCODE_BADNODECLASSINVALID;
UA_NodeId resolvedMethodId;
UA_StatusCode res =
findChildByBrowsename(server, session, callObject->head.nodeId,
UA_NODECLASS_METHOD, UA_REFERENCETYPEINDEX_HASCOMPONENT,
UA_NS0ID(HASCOMPONENT), &callMethod->head.browseName,
&resolvedMethodId);
if(res != UA_STATUSCODE_GOOD)
return (res == UA_STATUSCODE_BADNOTFOUND) ?
UA_STATUSCODE_BADMETHODINVALID : res;
const UA_MethodNode *resolvedMethod = (const UA_MethodNode*)callMethod;
if(!UA_NodeId_equal(&resolvedMethodId, &callMethod->head.nodeId)) {
res = checkMethodInTypeHierarchy(server, session,
&callObject->head.nodeId,
&callMethod->head.nodeId,
&callMethod->head.browseName);
if(res != UA_STATUSCODE_GOOD) {
if(res == UA_STATUSCODE_BADNOTFOUND)
res = UA_STATUSCODE_BADMETHODINVALID;
goto errout;
}
resolvedMethod = (const UA_MethodNode*)
UA_NODESTORE_GET_SELECTIVE(server, &resolvedMethodId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_EXECUTABLE,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!resolvedMethod) {
res = UA_STATUSCODE_BADINTERNALERROR;
goto errout;
}
}
res = callWithResolvedMethodAndObject(server, session, request, result,
resolvedMethod, callObject);
errout:
if(resolvedMethod && (const UA_Node*)resolvedMethod != callMethod)
UA_NODESTORE_RELEASE(server, (const UA_Node*)resolvedMethod);
UA_NodeId_clear(&resolvedMethodId);
return res;
}
UA_Boolean
Operation_CallMethod(UA_Server *server, UA_Session *session,
const UA_CallMethodRequest *request,
UA_CallMethodResult *result) {
UA_LOCK_ASSERT(&server->serviceMutex);
const UA_Node *method =
UA_NODESTORE_GET_SELECTIVE(server, &request->methodId,
UA_NODEATTRIBUTESMASK_NODECLASS |
UA_NODEATTRIBUTESMASK_BROWSENAME,
UA_REFERENCETYPESET_NONE,
UA_BROWSEDIRECTION_INVALID);
if(!method) {
result->statusCode = UA_STATUSCODE_BADMETHODINVALID;
return true;
}
const UA_Node *object =
UA_NODESTORE_GET_SELECTIVE(server, &request->objectId,
UA_NODEATTRIBUTESMASK_NODECLASS,
UA_REFERENCETYPESET_ALL,
UA_BROWSEDIRECTION_BOTH);
if(!object) {
result->statusCode = UA_STATUSCODE_BADNODEIDUNKNOWN;
UA_NODESTORE_RELEASE(server, method);
return true;
}
result->statusCode =
callWithMethodAndObject(server, session, request,
result, method, object);
UA_NODESTORE_RELEASE(server, method);
UA_NODESTORE_RELEASE(server, object);
return (result->statusCode != UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY);
}
UA_CallMethodResult
UA_Server_call(UA_Server *server, const UA_CallMethodRequest *request) {
UA_CallMethodResult result;
UA_CallMethodResult_init(&result);
lockServer(server);
Operation_CallMethod(server, &server->adminSession, request, &result);
if(result.statusCode == UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY) {
if(server->config.asyncOperationCancelCallback)
server->config.asyncOperationCancelCallback(server, result.outputArguments);
result.statusCode = UA_STATUSCODE_BADWAITINGFORRESPONSE;
}
unlockServer(server);
return result;
}
#endif