/*------------------------------------------------------------------
 * us5399.c - Unit Tests for User Story 5399 - Test DTLS max active 
 *            sessions API
 *
 * August, 2019
 *
 * Copyright (c) 2019 by cisco Systems, Inc.
 * All rights reserved.
 *------------------------------------------------------------------
 */
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif 
#include <est.h>
#include <curl/curl.h>
#include "curl_utils.h"
#include "test_utils.h"
#include "st_server.h"
#include <openssl/ssl.h>

#ifdef HAVE_CUNIT
#include "CUnit/Basic.h"
#include "CUnit/Automated.h"
#endif

#define EST_UT_MAX_CMD_LEN       512
#define US5399_SERVER_IP         "127.0.0.1"
#define US5399_CSSL_NOT_SET_MSG "The path for the openssl installation used by"\
" the python emulator was not specified.\n Please set the environment variable"\
" COAP_EMU_SSL"

#ifndef WIN32
#define US5399_CACERTS       "CA/estCA/cacert.crt"
#define US5399_EXPLICIT_CERT "US5399/cert-RA.pem" 
#define US5399_EXPLICIT_KEY  "US5399/key-RA.pem"
#define US5399_TRUSTED_CERT  "CA/trustedcerts.crt"
#define US5399_SERVER_CERT_AND_KEY "CA/estCA/private/estservercertandkey.pem"
#define US5399_HUGE_TRUSTED_CERT "CA/mfgCAs/trustedcertswithhugesudichain.crt"
#define US5399_HUGE_CERT_AND_KEY "CA/mfgCAs/sudiCA/private/certandkey2-estHugeSUDI.pem"
#define US5399_HUGER_TRUSTED_CERT "CA/mfgCAs/trustedcertswithhugersudichain.crt"
#define US5399_HUGER_CERT_AND_KEY "CA/mfgCAs/sudiCA/private/certandkey2-estHugerSUDI.pem"
#define US5399_MIDSIZE_CSR "US5399/midsize.csr"
#else
#define US5399_CACERTS       "CA\\estCA\\cacert.crt"
#define US5399_EXPLICIT_CERT "US5399\\cert-RA.pem" 
#define US5399_EXPLICIT_KEY  "US5399\\key-RA.pem"
#define US5399_TRUSTED_CERT  "CA\\trustedcerts.crt"
#define US5399_SERVER_CERT_AND_KEY "CA\\estCA\\private\\estservercertandkey.pem"
#define US5399_HUGE_TRUSTED_CERT "CA\\mfgCAs\\trustedcertswithhugesudichain.crt"
#define US5399_HUGE_CERT_AND_KEY "CA\\mfgCAs\\sudiCA\\private\\certandkey2-estHugeSUDI.pem"
#define US5399_HUGER_TRUSTED_CERT "CA\\mfgCAs\\trustedcertswithhugersudichain.crt"
#define US5399_HUGER_CERT_AND_KEY "CA\\mfgCAs\\sudiCA\\private\\certandkey2-estHugerSUDI.pem"
#define US5399_MIDSIZE_CSR "US5399\\midsize.csr"
#endif

static unsigned char *cacerts = NULL;
static int cacerts_len = 0;

static char *cssl_emulator_path = NULL;

static int coap_mode_support = 0;

/* CU_pSuite coap_sanity_psuite = NULL; */

/*
 * Used to start server in CoAP mode
 */
#define US5399_SERVER_PORT      29001
/*
 * Used to test the CoAP init API function
 */
#define US5399_API_TEST_PORT     29002


static void us5399_clean (void)
{
}

static int us5399_start_server (int manual_enroll, int nid)
{
    int rv;

    rv = st_start_coap(US5399_SERVER_PORT,
                       US5399_SERVER_CERT_AND_KEY,
                       US5399_SERVER_CERT_AND_KEY,
                       "US5399 test realm",
                       US5399_CACERTS,
                       US5399_TRUSTED_CERT,
                       "CA/estExampleCA.cnf",
                       manual_enroll,
                       0,
                       nid);
    if (rv != EST_ERR_NONE) {
        printf("Failed to start CoAP st_server\n");
    }

    st_set_default_est_event_callbacks();
    
    return rv;

}

/*
 * This routine is called when CUnit initializes this test
 * suite.  This can be used to allocate data or open any
 * resources required for all the test cases.
 */
static int us5399_init_suite (void)
{
    int rv;

    est_init_logger(EST_LOG_LVL_INFO, NULL);

    /*
     * Read in the CA certificates
     */
    cacerts_len = read_binary_file(US5399_CACERTS, &cacerts);
    if (cacerts_len <= 0) {
        return 1;
    }

    us5399_clean();

    /*
     * Start an instance of the EST server with
     * automatic enrollment enabled.
     */
    rv = us5399_start_server(0, 0);

    cssl_emulator_path = getenv("COAP_EMU_SSL");
    
    return rv;
}

/*
 * This routine is called when CUnit uninitializes this test
 * suite.  This can be used to deallocate data or close any
 * resources that were used for the test cases.
 */
static int us5399_destroy_suite (void)
{
    st_stop();
    free(cacerts);
    return 0;
}


/*
 * Test the DTLS max sessions API function,
 * est_server_set_dtls_session_max()
 */
static void us5399_test1 (void)
{
    EST_CTX *ectx;
    BIO *certin, *keyin;
    X509 *x;
    EVP_PKEY *priv_key;

    unsigned char *trustcerts = NULL;
    int trustcerts_len = 0;

    unsigned char *cacerts = NULL;
    int cacerts_len = 0;

    EST_ERROR est_rc;
    
    LOG_FUNC_NM;
    
    /*
     * Need to get a valid EST context in server mode.  To do that, need
     * to call est_server_init().  To do that, need to set up a bunch of
     * parameters to be passed in.
     */

    /*
     * The server's ID certificate.
     */
    certin = BIO_new(BIO_s_file());
    if (BIO_read_filename(certin, US5399_SERVER_CERT_AND_KEY) <= 0) {
        printf("Unable to read server certificate file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }    
    /*
     * Read the file, which is expected to be PEM encoded.
     */
    x = PEM_read_bio_X509(certin, NULL, NULL, NULL);
    if (x == NULL) {
        printf("Error while reading PEM encoded server certificate file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    BIO_free(certin);

    /*
     * Read in the server's private key
     */
    keyin = BIO_new(BIO_s_file());
    if (BIO_read_filename(keyin, US5399_SERVER_CERT_AND_KEY) <= 0) {
        printf("Unable to read server private key file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    /*
     * Read in the private key file, which is expected to be a PEM
     * encoded private key.
     */
    priv_key = PEM_read_bio_PrivateKey(keyin, NULL, NULL, NULL);
    if (priv_key == NULL) {
        printf("Error while reading PEM encoded private key file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    BIO_free(keyin);
    
    /*
     * CA certs to use as the trust store
     */
    trustcerts_len = read_binary_file(US5399_TRUSTED_CERT, &trustcerts);
    if (trustcerts_len <= 0) {
        printf("Trusted certs file %s could not be read\n",
               US5399_TRUSTED_CERT);
        return;
    }

    /*
     * Read in the CA certs to use as response to /cacerts responses
     */
    cacerts_len = read_binary_file(US5399_CACERTS, &cacerts);
    if (cacerts_len <= 0) {
        printf("CA chain file %s file could not be read\n", US5399_CACERTS);
        return;
    }

    /*
     * Initialize the library and get an EST context
     */
    ectx = est_server_init(trustcerts, trustcerts_len, cacerts, cacerts_len,
                           EST_CERT_FORMAT_PEM, "estrealm", x, priv_key);
    if (!ectx) {
        printf("Unable to initialize EST context.  Aborting!!!\n");
        return;
    }

    /*
     * AND finally, test the dtls max sessions API
     *
     * No context
     */
    est_rc = est_server_set_dtls_session_max(NULL, EST_DTLS_SESSION_MAX_MIN);

    CU_ASSERT(est_rc == EST_ERR_NO_CTX);
    if (est_rc != EST_ERR_NO_CTX) {
        printf("Failed to detect no EST context passed in\n");
    }

    /*
     * A valid max sessions value
     */
    est_rc = est_server_set_dtls_session_max(ectx, EST_DTLS_SESSION_MAX_MIN);

    CU_ASSERT(est_rc == EST_ERR_NONE);
    if (est_rc != EST_ERR_NONE) {
        printf("Failed to set the minimum setting for dtls max sessions\n");
    }

    /*
     * maximum dtls max sessions value 
     */
    est_rc = est_server_set_dtls_session_max(ectx, EST_DTLS_SESSION_MAX_MAX);

    CU_ASSERT(est_rc == EST_ERR_NONE);
    if (est_rc != EST_ERR_NONE) {
        printf("Failed set the maximum dtls max sessions value\n");
    }
    
    /*
     * maximum +1 dtls max sessions value 
     */
    est_rc = est_server_set_dtls_session_max(ectx, EST_DTLS_SESSION_MAX_MAX+1);

    CU_ASSERT(est_rc == EST_ERR_INVALID_PARAMETERS);
    if (est_rc != EST_ERR_INVALID_PARAMETERS) {
        printf("Failed to catch the max+1 dtls max sessions value\n");
    }

    if (priv_key) {
        EVP_PKEY_free(priv_key);
    }
    if (x) {
        X509_free(x);
    }
    if (cacerts) {
        free(cacerts);
    }
    if (trustcerts) {
        free(trustcerts);
    }    
}

/*
 * Test the DTLS max sessions API function,
 * est_server_set_dtls_session_max().
 *
 * Test the case where a call is made to set the DTLS max sessions
 * after the server has been started.  Should result in an error
 * response of EST_ERR_SERVER_ALREADY_OPERATIONAL.
 */
static void us5399_test2 (void)
{
    EST_CTX *ectx;
    BIO *certin, *keyin;
    X509 *x;
    EVP_PKEY *priv_key;

    unsigned char *trustcerts = NULL;
    int trustcerts_len = 0;

    unsigned char *cacerts = NULL;
    int cacerts_len = 0;

    EST_ERROR est_rc;
    
    LOG_FUNC_NM;
    
    /*
     * Need to get a valid EST context in server mode.  To do that, need
     * to call est_server_init().  To do that, need to set up a bunch of
     * parameters to be passed in.
     */

    /*
     * The server's ID certificate.
     */
    certin = BIO_new(BIO_s_file());
    if (BIO_read_filename(certin, US5399_SERVER_CERT_AND_KEY) <= 0) {
        printf("Unable to read server certificate file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }    
    /*
     * Read the file, which is expected to be PEM encoded.
     */
    x = PEM_read_bio_X509(certin, NULL, NULL, NULL);
    if (x == NULL) {
        printf("Error while reading PEM encoded server certificate file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    BIO_free(certin);

    /*
     * Read in the server's private key
     */
    keyin = BIO_new(BIO_s_file());
    if (BIO_read_filename(keyin, US5399_SERVER_CERT_AND_KEY) <= 0) {
        printf("Unable to read server private key file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    /*
     * Read in the private key file, which is expected to be a PEM
     * encoded private key.
     */
    priv_key = PEM_read_bio_PrivateKey(keyin, NULL, NULL, NULL);
    if (priv_key == NULL) {
        printf("Error while reading PEM encoded private key file %s\n",
               US5399_SERVER_CERT_AND_KEY);
        return;
    }
    BIO_free(keyin);
    
    /*
     * CA certs to use as the trust store
     */
    trustcerts_len = read_binary_file(US5399_TRUSTED_CERT, &trustcerts);
    if (trustcerts_len <= 0) {
        printf("Trusted certs file %s could not be read\n",
               US5399_TRUSTED_CERT);
        return;
    }

    /*
     * Read in the CA certs to use as response to /cacerts responses
     */
    cacerts_len = read_binary_file(US5399_CACERTS, &cacerts);
    if (cacerts_len <= 0) {
        printf("CA chain file %s file could not be read\n", US5399_CACERTS);
        return;
    }

    /*
     * Initialize the library and get an EST context
     */
    ectx = est_server_init(trustcerts, trustcerts_len, cacerts, cacerts_len,
                           EST_CERT_FORMAT_PEM, "estrealm", x, priv_key);
    if (!ectx) {
        printf("Unable to initialize EST context.\n");
        return;
    }

    est_rc = est_server_coap_init_start(ectx, 8888);
    if (est_rc != EST_ERR_NONE) {
        printf("Failed to start the EST library in server mode.\n");
        return;
    }
    
    /*
     * AND finally, test the dtls max sessions API
     *
     * A valid max sessions value
     */
    est_rc = est_server_set_dtls_session_max(ectx, EST_DTLS_SESSION_MAX_MIN);

    CU_ASSERT(est_rc == EST_ERR_SERVER_ALREADY_OPERATIONAL);
    if (est_rc != EST_ERR_SERVER_ALREADY_OPERATIONAL) {
        printf("Failed to prevent the max sessions setting when operational.\n");
    }

    est_rc = est_server_stop(ectx);
    if (est_rc != EST_ERR_NONE) {
        printf("Failed to stop the EST library in server mode.\n");
        return;
    }

    if (priv_key) {
        EVP_PKEY_free(priv_key);
    }
    if (x) {
        X509_free(x);
    }
    if (cacerts) {
        free(cacerts);
    }
    if (trustcerts) {
        free(trustcerts);
    }
}

/* The main() function for setting up and running the tests.
 * Returns a CUE_SUCCESS on successful running, another
 * CUnit error code on failure.
 */
int us5399_add_suite (void)
{
#ifdef HAVE_CUNIT
    CU_pSuite pSuite = NULL;

    /*
     * check to see if coap mode support has been compiled in
     */
    if (!coap_mode_supported(US5399_SERVER_CERT_AND_KEY, US5399_TRUSTED_CERT,
                             US5399_CACERTS, US5399_API_TEST_PORT)) {
        printf("CoAP mode is not supported in this build of EST.  Rebuild "
               "using --with-libcoap-dir= \n");
        coap_mode_support = 0;
        return 0;
    }
    coap_mode_support = 1;
    
    /* add a suite to the registry */
    pSuite = CU_add_suite("us5399_DTLS_max_sessions_API",
            us5399_init_suite,
            us5399_destroy_suite);
    if (NULL == pSuite) {
        CU_cleanup_registry();
        return CU_get_error();
    }

    if (coap_mode_support) {

        /* add the tests to the suite */
        if ((NULL == CU_add_test(pSuite, "Test dtls max sessions API", us5399_test1)) ||
            (NULL == CU_add_test(pSuite, "Test dtls max sessions after start", us5399_test2))
            ) {
            CU_cleanup_registry();
            return CU_get_error();
        }
    }

    return CUE_SUCCESS;
#endif
}

