/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* sockperf.c
 * This simple network client tries to connect to an echo daemon (echod)
 * listening on a port it supplies, then time how long it takes to
 * reply with packets of varying sizes.
 * It prints results once completed.
 *
 * To run,
 *
 *   ./echod &
 *   ./sockperf
 */

#include <stdio.h>
#include <stdlib.h>  /* for atexit() */

#include "apr.h"
#include "apr_network_io.h"
#include "apr_strings.h"

#define MAX_ITERS    10
#define TEST_SIZE  1024

struct testSet {
    char c;
    apr_size_t size;
    int iters;
} testRuns[] = {
    { 'a', 1, 3 },
    { 'b', 4, 3 },
    { 'c', 16, 5 },
    { 'd', 64, 5 },
    { 'e', 256, 10 },
};

struct testResult {
    int size;
    int iters;
    apr_time_t msecs[MAX_ITERS];
    apr_time_t avg;
};

static apr_int16_t testPort = 4747;
static apr_sockaddr_t *sockAddr = NULL;

static void reportError(const char *msg, apr_status_t rv, 
                        apr_pool_t *pool)
{
    fprintf(stderr, "%s\n", msg);
    if (rv != APR_SUCCESS)
        fprintf(stderr, "Error: %d\n'%s'\n", rv,
                apr_psprintf(pool, "%pm", &rv));
    
}

static void closeConnection(apr_socket_t *sock)
{
    apr_size_t len = 0;
    apr_socket_send(sock, NULL, &len);
}

static apr_status_t sendRecvBuffer(apr_time_t *t, const char *buf, 
                                   apr_size_t size, apr_pool_t *pool)
{
    apr_socket_t *sock;
    apr_status_t rv;
    apr_size_t len = size, thistime = size;
    char *recvBuf;
    apr_time_t testStart = apr_time_now(), testEnd;
    int i;

    if (! sockAddr) {
        rv = apr_sockaddr_info_get(&sockAddr, "127.0.0.1", APR_UNSPEC,
                                   testPort, 0, pool);
        if (rv != APR_SUCCESS) {
            reportError("Unable to get socket info", rv, pool);
            return rv;
        }

        /* make sure we can connect to daemon before we try tests */

        rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, APR_PROTO_TCP,
                           pool);
        if (rv != APR_SUCCESS) {
            reportError("Unable to create IPv4 stream socket", rv, pool);
            return rv;
        }

        rv = apr_socket_connect(sock, sockAddr);
        if (rv != APR_SUCCESS) {
            reportError("Unable to connect to echod!", rv, pool);
            apr_socket_close(sock);
            return rv;
        }
        apr_socket_close(sock);

    }

    recvBuf = apr_palloc(pool, size);
    if (! recvBuf) {
        reportError("Unable to allocate buffer", ENOMEM, pool);
        return ENOMEM;
    }

    *t = 0;

    /* START! */
    testStart = apr_time_now();
    rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, APR_PROTO_TCP,
                           pool);
    if (rv != APR_SUCCESS) {
        reportError("Unable to create IPv4 stream socket", rv, pool);
        return rv;
    }

    rv = apr_socket_connect(sock, sockAddr);
    if (rv != APR_SUCCESS) {
        reportError("Unable to connect to echod!", rv, pool);
        apr_socket_close(sock);
        return rv;
    }

    for (i = 0; i < 3; i++) {

        len = size;
        thistime = size;

        rv = apr_socket_send(sock, buf, &len);
        if (rv != APR_SUCCESS || len != size) {
            reportError(apr_psprintf(pool, 
                         "Unable to send data correctly (iteration %d of 3)",
                         i) , rv, pool);
            closeConnection(sock);
            apr_socket_close(sock);
            return rv;
        }
    
        do {
            len = thistime;
            rv = apr_socket_recv(sock, &recvBuf[size - thistime], &len);
            if (rv != APR_SUCCESS) {
                reportError("Error receiving from socket", rv, pool);
                break;
            }
            thistime -= len;
        } while (thistime);
    }

    closeConnection(sock);
    apr_socket_close(sock);
    testEnd = apr_time_now();
    /* STOP! */

    if (thistime) {
        reportError("Received less than we sent :-(", rv, pool);
        return rv;
    }        
    if (strncmp(recvBuf, buf, size) != 0) {
        reportError("Received corrupt data :-(", 0, pool);
        printf("We sent:\n%s\nWe received:\n%s\n", buf, recvBuf);
        return EINVAL;
    }
    *t = testEnd - testStart;
    return APR_SUCCESS;
}

static apr_status_t runTest(struct testSet *ts, struct testResult *res,
                            apr_pool_t *pool)
{
    char *buffer;
    apr_status_t rv = APR_SUCCESS;
    int i;
    apr_size_t sz = ts->size * TEST_SIZE;
    
    buffer = apr_palloc(pool, sz);
    if (!buffer) {
        reportError("Unable to allocate buffer", ENOMEM, pool);
        return ENOMEM;
    }
    memset(buffer, ts->c, sz);

    res->iters = ts->iters > MAX_ITERS ? MAX_ITERS : ts->iters;

    for (i = 0; i < res->iters; i++) {
        apr_time_t iterTime;
        rv = sendRecvBuffer(&iterTime, buffer, sz, pool);
        if (rv != APR_SUCCESS) {
            res->iters = i;
            break;
        }
        res->msecs[i] = iterTime;
    }

    return rv;
}

int main(int argc, char **argv)
{
    apr_pool_t *pool;
    apr_status_t rv;
    int i;
    int nTests = sizeof(testRuns) / sizeof(testRuns[0]);
    struct testResult *results;

    printf("APR Test Application: sockperf\n");

    apr_initialize();
    atexit(apr_terminate);

    apr_pool_create(&pool, NULL);

    results = (struct testResult *)apr_pcalloc(pool, 
                                        sizeof(*results) * nTests);

    for (i = 0; i < nTests; i++) {
        printf("Test -> %c\n", testRuns[i].c);
        results[i].size = testRuns[i].size * (apr_size_t)TEST_SIZE;
        rv = runTest(&testRuns[i], &results[i], pool);
        if (rv != APR_SUCCESS) {
            /* error already reported */
            exit(1);
        }
    }

    printf("Tests Complete!\n");
    for (i = 0; i < nTests; i++) {
        int j;
        apr_time_t totTime = 0;
        printf("%10d byte block:\n", results[i].size);
        printf("\t%2d iterations : ", results[i].iters);
        for (j = 0; j < results[i].iters; j++) {
            printf("%6" APR_TIME_T_FMT, results[i].msecs[j]);
            totTime += results[i].msecs[j];
        }
        printf("<\n");
        printf("\t  Average: %6" APR_TIME_T_FMT "\n",
               totTime / results[i].iters);
    }

    return 0;
}
