/* Free the SSL/TLS connection data.
 *
 * ctx  The connection data.
 */
static void SSLConn_Free(SSLConn_CTX* ctx)
{
    int i;
    ThreadData* threadData;

    if (ctx == NULL)
        return;

    for (i = 0; i < ctx->numThreads; i++) {
        threadData = &ctx->threadData[i];

        while (threadData->sslConn != NULL)
            SSLConn_Close(ctx, threadData, threadData->sslConn);
        SSLConn_FreeSSLConn(threadData);
        WolfSSLCtx_Final(threadData->ctx);
    }
    free(ctx->threadData);

    free(ctx);
}
/* Main entry point for the program.
 *
 * argc  The count of command line arguments.
 * argv  The command line arguments.
 * returns 0 on success and 1 otherwise.
 */
int main(int argc, char* argv[])
{
    socklen_t    socketfd = -1;
    int          ch;
    WOLFSSL_CTX* ctx = NULL;
    SSLConn_CTX* sslConnCtx;
    word16       port = wolfSSLPort;
    int          resumeSession = 0;
    char*        cipherList = NULL;
    char*        ourCert       = CLI_CERT;
    char*        ourKey        = CLI_KEY;
    char*        verifyCert    = CA_CERT;
    int          version       = SERVER_DEFAULT_VERSION;
    int          numConns      = SSL_NUM_CONN;
    int          numBytesRead  = NUM_READ_BYTES;
    int          numBytesWrite = NUM_WRITE_BYTES;
    int          maxBytes      = MAX_BYTES;
    int          maxConns      = MAX_CONNECTIONS;
    int          i;

    /* Parse the command line arguments. */
    while ((ch = mygetopt(argc, argv, OPTIONS)) != -1) {
        switch (ch) {
            /* Help with command line options. */
            case '?':
                Usage();
                exit(EXIT_SUCCESS);

            /* Port number to connect to. */
            case 'p':
                port = (word16)atoi(myoptarg);
                break;

            /* Version of SSL/TLS to use. */
            case 'v':
                version = atoi(myoptarg);
                if (version < 0 || version > 3) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                break;

            /* List of cipher suites to use. */
            case 'l':
                cipherList = myoptarg;
                break;

            /* File name of client certificate for client authentication. */
            case 'c':
                ourCert = myoptarg;
                break;

            /* File name of client private key for client authentication. */
            case 'k':
                ourKey = myoptarg;
                break;

            /* File name of server certificate/CA for peer verification. */
            case 'A':
                verifyCert = myoptarg;
                break;

            /* Resume sessions. */
            case 'r':
                resumeSession = 1;
                break;

            /* Number of connections to make. */
            case 'n':
                maxConns = atoi(myoptarg);
                if (maxConns < 0 || maxConns > 1000000) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                maxBytes = 0;
                break;

            /* Number of conncurrent connections to use. */
            case 'N':
                numConns  = atoi(myoptarg);
                if (numConns < 0 || numConns > 1000000) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                break;

            /* Number of bytes to read each call. */
            case 'R':
                numBytesRead = atoi(myoptarg);
                if (numBytesRead <= 0) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                break;

            /* Number of bytes to write each call. */
            case 'W':
                numBytesWrite = atoi(myoptarg);
                if (numBytesWrite <= 0) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                break;

            /* Maximum number of read and write bytes (separate counts). */
            case 'B':
                maxBytes = atoi(myoptarg);
                if (maxBytes <= 0) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                maxConns = 0;
                break;

            /* Unrecognized command line argument. */
            default:
                Usage();
                exit(MY_EX_USAGE);
        }
    }


#ifdef DEBUG_WOLFSSL
    wolfSSL_Debugging_ON();
#endif

    /* Initialize wolfSSL */
    wolfSSL_Init();

    /* Initialize wolfSSL and create a context object. */
    if (WolfSSLCtx_Init(version, ourCert, ourKey, verifyCert, cipherList, &ctx)
            == EXIT_FAILURE)
        exit(EXIT_FAILURE);

    /* Create SSL/TLS connection data object. */
    sslConnCtx = SSLConn_New(numConns, numBytesRead, numBytesWrite,
                             maxConns, maxBytes, resumeSession);
    if (sslConnCtx == NULL)
        exit(EXIT_FAILURE);

    /* Keep handling connections until all done. */
    for (i = 0; !SSLConn_Done(sslConnCtx); i = (i + 1) % numConns) {
        SSLConn* sslConn = &sslConnCtx->sslConn[i];

        /* Perform close if in CLOSE state. */
        if (sslConn->state == CLOSE) {
            if (sslConnCtx->numConnections == 0) {
                WOLFSSL_CIPHER* cipher;
                cipher = wolfSSL_get_current_cipher(sslConn->ssl);
                printf("SSL cipher suite is %s\n",
                       wolfSSL_CIPHER_get_name(cipher));
            }
            SSLConn_Close(sslConnCtx, sslConn);
        }

        /* Create TCP connection and connect if in INIT state. */
        if ((sslConn->state == INIT) &&
            ((sslConnCtx->maxConnections <= 0) ||
             (sslConnCtx->numCreated < sslConnCtx->maxConnections))) {
            if (CreateSocketConnect(port, &socketfd) == EXIT_FAILURE) {
                printf("ERROR: failed to connect to server\n");
                exit(EXIT_FAILURE);
            }

            SSLConn_Connect(sslConnCtx, ctx, socketfd, sslConn);
        }

#ifdef WOLFSSL_ASYNC_CRYPT
        if (sslConn->err == 4) {
            int ret;
            double start;

            start = current_time(1);
            ret = wolfSSL_AsyncPoll(sslConn->ssl, WOLF_POLL_FLAG_CHECK_HW);
            sslConnCtx->asyncTime += current_time(0) - start;
            if (ret < 0) {
                printf("ERROR: failed in async polling\n");
                break;
            }
            if (ret == 0)
                continue;
        }
        sslConn->err = 0;
#endif

        /* Handle other SSL states. */
        if (sslConnCtx->totalTime == 0)
            sslConnCtx->totalTime = current_time(1);
        if (SSLConn_ReadWrite(sslConnCtx, sslConn) == EXIT_FAILURE) {
            if (sslConnCtx->maxConnections > 0)
                sslConn->state = CLOSE;
        }
    }

    sslConnCtx->totalTime = current_time(0) - sslConnCtx->totalTime;

    SSLConn_PrintStats(sslConnCtx);
    SSLConn_Free(sslConnCtx);

    WolfSSLCtx_Final(ctx);

    wolfSSL_Cleanup();

    exit(EXIT_SUCCESS);
}
/* Initialize the wolfSSL library and create a wolfSSL context.
 *
 * version      The protocol version.
 * cert         The server's certificate.
 * key          The server's private key matching the certificate.
 * verifyCert   The certificate for client authentication.
 * cipherList   The list of negotiable ciphers.
 * wolfsslCtx  The new wolfSSL context object.
 * returns EXIT_SUCCESS when a wolfSSL context object is created and
 * EXIT_FAILURE otherwise.
 */
static int WolfSSLCtx_Init(ThreadData* threadData, int version, int allowDowngrade,
    char* cert, char* key, char* verifyCert, char* cipherList)
{
    wolfSSL_method_func method = NULL;

    method = SSL_GetMethod(version, allowDowngrade);
    if (method == NULL)
        return(EXIT_FAILURE);

    /* Create and initialize WOLFSSL_CTX structure */
    if ((threadData->ctx = wolfSSL_CTX_new(method(NULL))) == NULL) {
        fprintf(stderr, "wolfSSL_CTX_new error.\n");
        return(EXIT_FAILURE);
    }

#ifdef WOLFSSL_ASYNC_CRYPT
#ifndef WC_NO_ASYNC_THREADING
    if (wolfAsync_DevOpenThread(&threadData->devId, &threadData->thread_id) < 0)
#else
    if (wolfAsync_DevOpen(&threadData->devId) < 0)
#endif
    {
        fprintf(stderr, "Async device open failed\nRunning without async\n");
    }

    wolfSSL_CTX_UseAsync(threadData->ctx, threadData->devId);
#endif

    /* Load server certificate into WOLFSSL_CTX */
    if (wolfSSL_CTX_use_certificate_file(threadData->ctx, cert, SSL_FILETYPE_PEM)
            != SSL_SUCCESS) {
        fprintf(stderr, "Error loading %s, please check the file.\n", cert);
        WolfSSLCtx_Final(threadData);
        return(EXIT_FAILURE);
    }

    /* Load server key into WOLFSSL_CTX */
    if (wolfSSL_CTX_use_PrivateKey_file(threadData->ctx, key, SSL_FILETYPE_PEM)
            != SSL_SUCCESS) {
        fprintf(stderr, "Error loading %s, please check the file.\n", key);
        WolfSSLCtx_Final(threadData);
        return(EXIT_FAILURE);
    }

    /* Setup client authentication. */
    wolfSSL_CTX_set_verify(threadData->ctx, SSL_VERIFY_PEER, 0);
    if (wolfSSL_CTX_load_verify_locations(threadData->ctx, verifyCert, 0) != SSL_SUCCESS) {
        fprintf(stderr, "Error loading %s, please check the file.\n",
                verifyCert);
        WolfSSLCtx_Final(threadData);
        return(EXIT_FAILURE);
    }

    if (cipherList != NULL) {
        if (wolfSSL_CTX_set_cipher_list(threadData->ctx, cipherList) != SSL_SUCCESS) {
            fprintf(stderr, "Server can't set cipher list.\n");
            WolfSSLCtx_Final(threadData);
            return(EXIT_FAILURE);
        }
    }

#ifndef NO_DH
    SetDHCtx(threadData->ctx);
#endif

    return EXIT_SUCCESS;
}