/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
 *
 *    Copyright 2019 (c) Kalycito Infotech Private Limited
 *    Copyright 2021 (c) Christian von Arnim, ISW University of Stuttgart (for VDW and umati)
 *    Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer)
 *    Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
 */

#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/create_certificate.h>
#include <open62541/plugin/securitypolicy.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/accesscontrol_default.h>

#include <signal.h>
#include <stdlib.h>

#if defined(UA_ENABLE_ENCRYPTION_MBEDTLS)
#include <mbedtls/version.h>
#endif

#include "common.h"

UA_Boolean running = true;
static void stopHandler(int sig) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "received ctrl-c");
    running = false;
}

int main(int argc, char* argv[]) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);
    UA_ByteString certificate = UA_BYTESTRING_NULL;
    UA_ByteString privateKey = UA_BYTESTRING_NULL;
    bool onlySecure = false;
    bool allowDiscovery = false;
    bool useEcc = false;
    const char *eccCertDir = NULL;

    /* Parse flags first so they are available during cert generation */
    for(int argpos = 1; argpos < argc; argpos++) {
        if(strcmp(argv[argpos], "--onlySecure") == 0) {
            onlySecure = true;
            continue;
        }
        if(strcmp(argv[argpos], "--allowDiscovery") == 0) {
            allowDiscovery = true;
            continue;
        }
        if(strcmp(argv[argpos], "--ecc") == 0) {
            useEcc = true;
            continue;
        }
        if(strcmp(argv[argpos], "--ecc-cert-dir") == 0 && argpos + 1 < argc) {
            eccCertDir = argv[++argpos];
            continue;
        }
    }

    if(argc >= 3) {
        /* Load certificate and private key */
        certificate = loadFile(argv[1]);
        privateKey = loadFile(argv[2]);
    } else {
        UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                     "Missing arguments. Arguments are "
                     "<server-certificate.der> <private-key.der> "
                     "[<trustlist1.crl>, ...] "
                     "[--onlySecure] "
                     "[--allowDiscovery] "
                     "[--ecc] "
                     "[--ecc-cert-dir <dir>]");
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                    "Trying to create a certificate.");

        UA_String subject[3] = {UA_STRING_STATIC("C=DE"),
                            UA_STRING_STATIC("O=SampleOrganization"),
                            UA_STRING_STATIC("CN=Open62541Server@localhost")};
        UA_UInt32 lenSubject = 3;
        UA_String subjectAltName[2]= {
            UA_STRING_STATIC("DNS:localhost"),
            UA_STRING_STATIC("URI:urn:open62541.unconfigured.application")
        };
        UA_UInt32 lenSubjectAltName = 2;
        UA_KeyValueMap *kvm = UA_KeyValueMap_new();
        UA_UInt16 expiresIn = 14;
        UA_KeyValueMap_setScalar(kvm, UA_QUALIFIEDNAME(0, "expires-in-days"),
                                 (void *)&expiresIn, &UA_TYPES[UA_TYPES_UINT16]);
        if(useEcc) {
            UA_String keyType = UA_STRING_STATIC("EC");
            UA_KeyValueMap_setScalar(kvm, UA_QUALIFIEDNAME(0, "key-type"),
                                     (void *)&keyType, &UA_TYPES[UA_TYPES_STRING]);
            UA_String eccCurve = UA_STRING_STATIC("prime256v1");
            UA_KeyValueMap_setScalar(kvm, UA_QUALIFIEDNAME(0, "ecc-curve"),
                                     (void *)&eccCurve, &UA_TYPES[UA_TYPES_STRING]);
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                        "Generating ECC (NIST P-256) certificate...");
        }
        UA_StatusCode statusCertGen = UA_CreateCertificate(
            UA_Log_Stdout, subject, lenSubject, subjectAltName, lenSubjectAltName,
            UA_CERTIFICATEFORMAT_DER, kvm, &privateKey, &certificate);
        UA_KeyValueMap_delete(kvm);

        if(statusCertGen != UA_STATUSCODE_GOOD) {
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                "Generating Certificate failed: %s",
                UA_StatusCode_name(statusCertGen));
            return EXIT_SUCCESS;
        }
    }

    /* Load the trustlist */
    size_t trustListSize = 0;
    if(argc > 3)
        trustListSize = (size_t)argc-3;
    UA_STACKARRAY(UA_ByteString, trustList, trustListSize+1);
    for(size_t i = 0; i < trustListSize; i++)
        trustList[i] = loadFile(argv[i+3]);

    /* Loading of an issuer list, not used in this application */
    size_t issuerListSize = 0;
    UA_ByteString *issuerList = NULL;

    /* Revocation lists are supported, but not used for the example here */
    UA_ByteString *revocationList = NULL;
    size_t revocationListSize = 0;

    UA_Server *server = UA_Server_new();
    UA_ServerConfig *config = UA_Server_getConfig(server);

    UA_StatusCode retval = UA_STATUSCODE_GOOD;
    if(onlySecure) {
        retval = UA_ServerConfig_setDefaultWithSecureSecurityPolicies(config, 4840,
                                                                      &certificate, &privateKey,
                                                                      trustList, trustListSize,
                                                                      issuerList, issuerListSize,
                                                                      revocationList, revocationListSize);
    } else {
        retval = UA_ServerConfig_setDefaultWithSecurityPolicies(config, 4840,
                                                                &certificate, &privateKey,
                                                                trustList, trustListSize,
                                                                issuerList, issuerListSize,
                                                                revocationList, revocationListSize);
    }

    /* Adds the None policy to the security policy list, but does not provide a None endpoint.
     * This enables a client to retrieve the server certificate and
     * all endpoints offered by a server. */
    if(onlySecure && allowDiscovery) {
        UA_ServerConfig_addSecurityPolicyNone(config, &certificate);
        config->securityPolicyNoneDiscoveryOnly = true;
    }

    /* --- ECC security policies from a certificate directory ---
     * Each ECC curve requires its own certificate/key pair. Place DER files in
     * a directory with names: server_c_<curve>.cert.der / server_c_<curve>.key.der
     * where <curve> is one of: nistP256, nistP384, brainpoolP256r1,
     * brainpoolP384r1, curve25519, curve448. */
    (void)eccCertDir; /* may be unused when ECC policies are compiled out */
#if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || \
    (defined(UA_ENABLE_ENCRYPTION_MBEDTLS) && defined(MBEDTLS_VERSION_NUMBER) && \
     MBEDTLS_VERSION_NUMBER >= 0x03000000)
    if(eccCertDir) {
        struct {
            const char *curve;
            UA_StatusCode (*addPolicy)(UA_ServerConfig *,
                                       const UA_ByteString *,
                                       const UA_ByteString *);
        } eccPolicies[] = {
            {"nistP256",        UA_ServerConfig_addSecurityPolicyEccNistP256},
            {"nistP384",        UA_ServerConfig_addSecurityPolicyEccNistP384},
            {"brainpoolP256r1", UA_ServerConfig_addSecurityPolicyEccBrainpoolP256r1},
            {"brainpoolP384r1", UA_ServerConfig_addSecurityPolicyEccBrainpoolP384r1},
#ifdef UA_ENABLE_ENCRYPTION_OPENSSL
            {"curve25519",      UA_ServerConfig_addSecurityPolicyEccCurve25519},
            {"curve448",        UA_ServerConfig_addSecurityPolicyEccCurve448}
#endif
        };
        size_t numEcc = sizeof(eccPolicies) / sizeof(eccPolicies[0]);

        for(size_t i = 0; i < numEcc; i++) {
            char certPath[512], keyPath[512];
            snprintf(certPath, sizeof(certPath),
                     "%s/server_c_%s.cert.der", eccCertDir, eccPolicies[i].curve);
            snprintf(keyPath, sizeof(keyPath),
                     "%s/server_c_%s.key.der", eccCertDir, eccPolicies[i].curve);

            UA_ByteString eccCert = loadFile(certPath);
            UA_ByteString eccKey  = loadFile(keyPath);
            if(eccCert.length == 0 || eccKey.length == 0) {
                UA_ByteString_clear(&eccCert);
                UA_ByteString_clear(&eccKey);
                continue;
            }

            UA_StatusCode rv = eccPolicies[i].addPolicy(config, &eccCert, &eccKey);
            if(rv == UA_STATUSCODE_GOOD) {
                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                            "Added ECC security policy: %s", eccPolicies[i].curve);
            } else {
                UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION,
                               "Could not add ECC_%s: %s",
                               eccPolicies[i].curve, UA_StatusCode_name(rv));
            }
            UA_ByteString_clear(&eccCert);
            UA_ByteString_clear(&eccKey);
        }

        /* Create endpoints for the newly added ECC policies.
         * addAllEndpoints has built-in duplicate detection. */
        UA_ServerConfig_addAllEndpoints(config);
    }
#endif

    /* Accept all certificates */
    config->secureChannelPKI.clear(&config->secureChannelPKI);
    UA_CertificateGroup_AcceptAll(&config->secureChannelPKI);

    config->sessionPKI.clear(&config->sessionPKI);
    UA_CertificateGroup_AcceptAll(&config->sessionPKI);

    /* Add username/password auth */
    UA_UsernamePasswordLogin login;
    login.password = UA_STRING("admin");
    login.username = UA_STRING("admin");
    config->accessControl.clear(&config->accessControl);
    const UA_String userTokenPolicy = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
    UA_AccessControl_default(config, true, &userTokenPolicy, 1, &login);

    UA_ByteString_clear(&certificate);
    UA_ByteString_clear(&privateKey);
    for(size_t i = 0; i < trustListSize; i++)
        UA_ByteString_clear(&trustList[i]);
    if(retval != UA_STATUSCODE_GOOD)
        goto cleanup;

    if(!running)
        goto cleanup; /* received ctrl-c already */
    
    retval = UA_Server_run(server, &running);

 cleanup:
    UA_Server_delete(server);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
