/* Create a new SSL/TLS connection data object.
 *
 * numConns   The number of concurrent connections.
 * bufferLen  The number of data bytes to read from server.
 * replyLen   The number of data bytes to send to servers.
 * maxConns   The number of connections to process this run.
 *            -1 indicates no maximum.
 * maxBytes   The number of bytes to send this run.
 *            -1 indicates no maximum.
 * returns an allocated and initialized connection data object or NULL on error.
 */
static SSLConn_CTX* SSLConn_New(int numConns, int bufferLen, int replyLen,
                                int maxConns, int maxBytes, int resume)
{
    SSLConn_CTX* ctx;
    int          i;

    ctx = (SSLConn_CTX*)malloc(sizeof(*ctx));
    if (ctx == NULL)
        return NULL;
    memset(ctx, 0, sizeof(*ctx));

    ctx->resume = resume;
    ctx->numConns = numConns;
    ctx->bufferLen = bufferLen;
    ctx->replyLen = replyLen;
    ctx->maxConnections = maxConns;
    ctx->maxBytes = maxBytes;

    /* Create an entry for each concurrent connection. */
    ctx->sslConn = (SSLConn*)malloc(ctx->numConns * sizeof(*ctx->sslConn));
    if (ctx->sslConn == NULL) {
        SSLConn_Free(ctx);
        return NULL;
    }
    for (i = 0; i < ctx->numConns; i++) {
        ctx->sslConn[i].sockfd = -1;
        ctx->sslConn[i].ssl = NULL;
        ctx->sslConn[i].session = NULL;
        ctx->sslConn[i].state = INIT;
        ctx->sslConn[i].err = 0;
    }

    /* Create a buffer for server data. */
    ctx->buffer = (char*)malloc(bufferLen);
    if (ctx->buffer == NULL) {
        SSLConn_Free(ctx);
        return NULL;
    }

    /* Create a reply that contains rand data. */
    ctx->reply = (char*)malloc(replyLen);
    if (ctx->reply == NULL) {
        SSLConn_Free(ctx);
        return NULL;
    }
    RandomReply(ctx->reply, replyLen);

    return ctx;
}
/* Create a new SSL/TLS connection data object.
 *
 * numThreads  The number of threads to create.
 * numConns    The maximum number of concurrent connections.
 * bufferLen   The number of data bytes to read from client.
 * replyLen    The number of data bytes to write to client.
 * maxConns    The number of connections to process this run.
 *             -1 indicates no maximum.
 * maxBytes    The number of bytes to send this run.
 *             -1 indicates no maximum.
 * returns an allocated and initialized connection data object or NULL on error.
 */
static SSLConn_CTX* SSLConn_New(int numThreads, int numConns, int bufferLen,
                                int replyLen, int maxConns, int maxBytes)
{
    SSLConn_CTX* ctx;
    ThreadData*  threadData;
    int          i;

    ctx = (SSLConn_CTX*)malloc(sizeof(*ctx));
    if (ctx == NULL)
        return NULL;
    memset(ctx, 0, sizeof(*ctx));

    ctx->numThreads = numThreads;
    ctx->numConns = numConns;
    ctx->bufferLen = bufferLen;
    ctx->replyLen = replyLen;
    ctx->maxConnections = maxConns;
    ctx->maxBytes = maxBytes;

    /* Pre-allocate the SSL connection data. */
    ctx->threadData = (ThreadData*)malloc(ctx->numThreads *
                                          sizeof(*ctx->threadData));
    if (ctx->threadData == NULL) {
        SSLConn_Free(ctx);
        return NULL;
    }
    for (i = 0; i < ctx->numThreads; i++) {
        threadData = &ctx->threadData[i];

        threadData->ctx = NULL;
        threadData->devId = INVALID_DEVID;
        threadData->sslConn = NULL;
        threadData->freeSSLConn = NULL;
        threadData->cnt = 0;
        threadData->thread_id = 0;
    }

    return 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[])
{
    int                 i;
    int                 ch;

    /* 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 listen on. */
            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 server certificate for authentication. */
            case 'c':
                ourCert = myoptarg;
                break;

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

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

            /* Number of connections to make. */
            case 't':
                numThreads  = atoi(myoptarg);
                if (numThreads < 0 || numThreads > 100) {
                    Usage();
                    exit(MY_EX_USAGE);
                }
                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 > 100000) {
                    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();

    RandomReply(reply, sizeof(reply));

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

    for (i = 0; i < numThreads; i++) {
        if (pthread_create(&sslConnCtx->threadData[i].thread_id, NULL,
                           ThreadHandler, &sslConnCtx->threadData[i]) < 0) {
            perror("ERRROR: could not create thread");
        }
    }

    /* Start all the threads. */
    for (i = 0; i < numThreads; i++)
        pthread_join(sslConnCtx->threadData[i].thread_id, NULL) ;

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

    SSLConn_PrintStats(sslConnCtx);
    SSLConn_Free(sslConnCtx);

    wolfSSL_Cleanup();

    exit(EXIT_SUCCESS);
}
/* 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);
}