/* 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 2019 (c) basysKom GmbH <opensource@basyskom.com> (Author: Frank Meerkötter)
 */

#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>

#include "server/ua_server_internal.h"
#include "server/ua_services.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "check.h"

static UA_Server *server = NULL;

static UA_StatusCode
methodCallback(UA_Server *serverArg,
         const UA_NodeId *sessionId, void *sessionHandle,
         const UA_NodeId *methodId, void *methodContext,
         const UA_NodeId *objectId, void *objectContext,
         size_t inputSize, const UA_Variant *input,
         size_t outputSize, UA_Variant *output)
{
    return UA_STATUSCODE_GOOD;
}

static void setup(void) {
    server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    UA_MethodAttributes noFpAttr = UA_MethodAttributes_default;
    noFpAttr.description = UA_LOCALIZEDTEXT("en-US","No function pointer attached");
    noFpAttr.displayName = UA_LOCALIZEDTEXT("en-US","No function pointer attached");
    noFpAttr.executable = true;
    noFpAttr.userExecutable = true;
    UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "nofunctionpointer"),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),
                            UA_QUALIFIEDNAME(1, "No function pointer"),
                            noFpAttr, NULL, // no callback
                            0, NULL, 0, NULL, NULL, NULL);

    UA_MethodAttributes nonExecAttr = UA_MethodAttributes_default;
    nonExecAttr.description = UA_LOCALIZEDTEXT("en-US","Not executable");
    nonExecAttr.displayName = UA_LOCALIZEDTEXT("en-US","Not executable");
    nonExecAttr.executable = false;
    nonExecAttr.userExecutable = true;
    UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "nonexec"),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),
                            UA_QUALIFIEDNAME(1, "Not executable"),
                            nonExecAttr, &methodCallback,
                            0, NULL, 0, NULL, NULL, NULL);
}

static void teardown(void) {
    UA_Server_delete(server);
}

START_TEST(callUnknownMethod) {
    const UA_UInt32 UA_NS0ID_UNKNOWN_METHOD = 60000;

    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_UNKNOWN_METHOD);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADNODEIDUNKNOWN);
} END_TEST

START_TEST(callKnownMethodOnUnknownObject) {
    const UA_UInt32 UA_NS0ID_UNKNOWN_OBJECT = 60000;

    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_REQUESTSERVERSTATECHANGE);
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_UNKNOWN_OBJECT);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADNODEIDUNKNOWN);
} END_TEST

START_TEST(callMethodAndObjectExistsButMethodHasWrongNodeClass) {
    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS);  // not a method
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADNODECLASSINVALID);
} END_TEST

START_TEST(callMethodOnUnrelatedObject) {
    /* Minimal nodeset does not add any method nodes we may call here */
#ifdef UA_GENERATED_NAMESPACE_ZERO
    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS);
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);  // not connected via hasComponent

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADMETHODINVALID);
#endif
} END_TEST

START_TEST(callMethodAndObjectExistsButNoFunctionPointerAttached) {
    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_STRING(1, "nofunctionpointer");
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADINTERNALERROR);
} END_TEST

START_TEST(callMethodNonExecutable) {
    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_STRING(1, "nonexec");
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADNOTEXECUTABLE);
} END_TEST

START_TEST(callMethodWithMissingArguments) {
/* Minimal nodeset does not add any method nodes we may call here */
#ifdef UA_GENERATED_NAMESPACE_ZERO
    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS);
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADARGUMENTSMISSING);
#endif
} END_TEST

START_TEST(callMethodWithTooManyArguments) {
/* Minimal nodeset does not add any method nodes we may call here */
#ifdef UA_GENERATED_NAMESPACE_ZERO
    UA_Variant inputArguments[2];
    UA_Variant_init(&inputArguments[0]);
    UA_Variant_init(&inputArguments[1]);

    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.inputArgumentsSize = 2;         // 1 would be correct
    callMethodRequest.inputArguments = (UA_Variant*)&inputArguments;
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS);
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADTOOMANYARGUMENTS);
#endif
} END_TEST

START_TEST(callMethodWithWronglyTypedArguments) {
/* Minimal nodeset does not add any method nodes we may call here */
#ifdef UA_GENERATED_NAMESPACE_ZERO
    UA_Variant inputArgument;
    UA_Variant_init(&inputArgument);
    UA_Double wrongType = 1.0;
    UA_Variant_setScalar(&inputArgument, &wrongType, &UA_TYPES[UA_TYPES_DOUBLE]);  // UA_UInt32 would be correct

    UA_CallMethodRequest callMethodRequest;
    UA_CallMethodRequest_init(&callMethodRequest);
    callMethodRequest.inputArgumentsSize = 1;
    callMethodRequest.inputArguments = &inputArgument;
    callMethodRequest.methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS);
    callMethodRequest.objectId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);

    UA_CallMethodResult result;
    UA_CallMethodResult_init(&result);
    result = UA_Server_call(server, &callMethodRequest);

    ck_assert_int_gt(result.inputArgumentResultsSize, 0);
    ck_assert_int_eq(result.inputArgumentResults[0], UA_STATUSCODE_BADTYPEMISMATCH);
    ck_assert_int_eq(result.statusCode, UA_STATUSCODE_BADINVALIDARGUMENT);

    UA_Array_delete(result.inputArgumentResults, result.inputArgumentResultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
#endif
} END_TEST

int main(void) {
    Suite *s = suite_create("services_call");

    TCase *tc_call = tcase_create("call - error branches");
    tcase_add_checked_fixture(tc_call, setup, teardown);
    tcase_add_test(tc_call, callUnknownMethod);
    tcase_add_test(tc_call, callKnownMethodOnUnknownObject);
    tcase_add_test(tc_call, callMethodAndObjectExistsButMethodHasWrongNodeClass);
    tcase_add_test(tc_call, callMethodAndObjectExistsButNoFunctionPointerAttached);
    tcase_add_test(tc_call, callMethodNonExecutable);
    tcase_add_test(tc_call, callMethodOnUnrelatedObject);
    tcase_add_test(tc_call, callMethodWithMissingArguments);
    tcase_add_test(tc_call, callMethodWithTooManyArguments);
    tcase_add_test(tc_call, callMethodWithWronglyTypedArguments);
    suite_add_tcase(s, tc_call);

    SRunner *sr = srunner_create(s);
    srunner_set_fork_status(sr, CK_NOFORK);
    srunner_run_all(sr, CK_NORMAL);
    int number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);
    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}