/*
**  OSSP sa - Socket Abstraction
**  Copyright (c) 2001-2004 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2001-2004 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2001-2004 Cable & Wireless <http://www.cw.com/>
**
**  This file is part of OSSP sa, a socket abstraction library which
**  can be found at http://www.ossp.org/pkg/lib/sa/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  sa_test.c: socket abstraction library test suite
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "ts.h"
#include "sa.h"

/* test: address import/export */
TS_TEST(test_saa_impexp)
{
    sa_addr_t *saa;
    sa_rc_t rv;
    char *cp;
    int i;
    struct {
        char *in;
        sa_rc_t rv;
        char *out;
        char *out_alt;
    } table[] = {
        /* positive tests */
        { "inet://0.0.0.0:0", SA_OK, "inet://0.0.0.0:0", NULL },
        { "inet://127.0.0.1:514", SA_OK, "inet://127.0.0.1:514", NULL },
        { "inet://localhost:syslog#udp", SA_OK, "inet://127.0.0.1:514", "inet://[::1]:514" },
        { "inet://localhost:smtp", SA_OK, "inet://127.0.0.1:25", "inet://[::1]:25" },
        { "unix:/tmp/socket", SA_OK, "unix:/tmp/socket", NULL },
        /* negative tests */
        { "inet:", SA_ERR_ARG, NULL, NULL },
        { "inet://1.2.3.4.5:0", SA_ERR_ARG, NULL, NULL },
        { "inet://just-hostname", SA_ERR_ARG, NULL, NULL },
        { "unix:", SA_ERR_ARG, NULL, NULL }
    };

    ts_test_check(TS_CTX, "sa_addr_create");
    if ((rv = sa_addr_create(&saa)) != SA_OK)
        ts_test_fail(TS_CTX, "sa_addr_create -> %d[%s] (expected %d[%s])",
                     rv, sa_error(rv), SA_OK, sa_error(SA_OK));
    for (i = 0; i < (int)(sizeof(table)/sizeof(table[0])); i++) {
        ts_test_check(TS_CTX, "sa_addr_u2a(\"%s\")", table[i].in);
        if ((rv = sa_addr_u2a(saa, table[i].in)) != table[i].rv)
            ts_test_fail(TS_CTX, "sa_addr_a2u -> %d[%s] (expected %d[%s])",
                         rv, sa_error(rv), table[i].rv, sa_error(table[i].rv));
        ts_test_check(TS_CTX, "sa_addr_a2u");
        if ((rv = sa_addr_a2u(saa, &cp)) != SA_OK) {
            ts_test_fail(TS_CTX, "sa_addr_u2a -> %d[%s] (expected %d[%s])",
                         rv, sa_error(rv), SA_OK, sa_error(SA_OK));
            continue;
        }
        if (table[i].rv == SA_OK) {
            if (table[i].out_alt != NULL) {
                if (strcmp(cp, table[i].out) != 0 && strcmp(cp, table[i].out_alt) != 0)
                    ts_test_fail(TS_CTX, "sa_addr_a2u -> \"%s\" (expected \"%s\" or \"%s\")",
                                 cp, table[i].out, table[i].out_alt);
            }
            else {
                if (strcmp(cp, table[i].out) != 0)
                    ts_test_fail(TS_CTX, "sa_addr_a2u -> \"%s\" (expected \"%s\")",
                                 cp, table[i].out);
            }
        }
        free(cp);
    }
    ts_test_check(TS_CTX, "sa_addr_destroy");
    if ((rv = sa_addr_destroy(saa)) != SA_OK)
        ts_test_fail(TS_CTX, "sa_addr_destroy -> %d[%s] (expected %d[%s])",
                     rv, sa_error(rv), SA_OK, sa_error(SA_OK));
}

/* test: address matching */
TS_TEST(test_saa_match)
{
    sa_addr_t *saa1;
    sa_addr_t *saa2;
    sa_rc_t rv;
    int i;
    struct {
        char *in;
        char *acl;
        int prefixlen;
        sa_rc_t rv;
    } table[] = {
        { "unix:/foo/bar", "unix:/foo/bar", -1, SA_OK },
        { "unix:/foo/bar", "unix:/foo/bar", 0, SA_OK },
        { "unix:/foo/bar", "unix:/foo", 4, SA_OK },
        { "unix:/foo/bar", "unix:/foo/quux", 4, SA_OK },
        { "unix:/foo/bar", "unix:/baz/quux", -1, SA_ERR_MTC },
        { "inet://0.0.0.0:0", "inet://0.0.0.0:0", 0, SA_OK },
        { "inet://127.0.0.1:514", "inet://127.0.0.1:514", -1, SA_OK },
        { "inet://127.0.0.1:514", "inet://0.0.0.0:0", 0, SA_OK },
        { "inet://127.0.0.1:514", "inet://12.34.56.78:9", 0, SA_OK },
        { "inet://127.0.0.1:514", "inet://12.34.56.78:9", -1, SA_ERR_MTC },
        { "inet://127.0.0.1:514", "inet://127.0.0.0:0", 24, SA_OK },
        { "inet://127.0.0.1:514", "inet://127.0.0.0:0", 32, SA_ERR_MTC },
        { "inet://141.1.23.20:25", "inet://141.1.23.40:25", 32, SA_ERR_MTC },
        { "inet://141.1.23.20:25", "inet://141.1.23.40:25", 24, SA_OK }
    };

    sa_addr_create(&saa1);
    sa_addr_create(&saa2);
    for (i = 0; i < (int)(sizeof(table)/sizeof(table[0])); i++) {
        if ((rv = sa_addr_u2a(saa1, table[i].in)) != SA_OK)
            continue;
        if ((rv = sa_addr_u2a(saa2, table[i].acl)) != SA_OK)
            continue;
        ts_test_check(TS_CTX, "sa_addr_match(\"%s\", \"%s\", %d)",
                      table[i].in, table[i].acl, table[i].prefixlen);
        if ((rv = sa_addr_match(saa1, saa2, table[i].prefixlen)) != table[i].rv)
            ts_test_fail(TS_CTX, "sa_addr_match -> %d[%s] (expected %d[%s])",
                         rv, sa_error(rv), table[i].rv, sa_error(table[i].rv));
    }
    sa_addr_destroy(saa1);
    sa_addr_destroy(saa2);
}

#define ex(proc, cmd) \
    do { \
        sa_rc_t __rv; \
        ts_test_check(TS_CTX, "%s: %s", #proc, #cmd); \
        if ((__rv = (cmd)) != SA_OK) { \
            if (__rv == SA_ERR_SYS) \
                ts_test_fail(TS_CTX, "%s -> error: %s (rc=%d) (system: %s)", \
                             #cmd, sa_error(__rv), __rv, strerror(errno)); \
            else \
                ts_test_fail(TS_CTX, "%s -> error: %s (rc=%d)", \
                             #cmd, sa_error(__rv), __rv); \
        } \
    } while (0)

/* test: client/server stream communication */
TS_TEST(test_sa_stream)
{
    sa_t *sa_clt;
    sa_t *sa_srv;
    sa_addr_t *saa_srv;
    sa_addr_t *saa_clt;
    pid_t pid_srv;
    sa_t *sa_cld;
    sa_addr_t *saa_cld;
    char buf_srv[1024];
    char buf_clt[1024];
    size_t l;

#define MSG_TCP_SRV_WELCOME "Welcome client\n"
#define MSG_TCP_CLT_WELCOME "Welcome server\n"
#define MSG_TCP_SRV_GOODBYE "Goodbye client\n"
#define MSG_TCP_CLT_GOODBYE "Goodbye server\n"

    if ((pid_srv = fork()) == 0) {
        /*
        ** SERVER
        */

        /* create server socket */
        ex(SRV, sa_create(&sa_srv));
        ex(SRV, sa_option(sa_srv, SA_OPTION_REUSEADDR, 1));
        ex(SRV, sa_option(sa_srv, SA_OPTION_REUSEPORT, 1));
        ex(SRV, sa_timeout(sa_srv, SA_TIMEOUT_ALL, 10, 0));

        /* bind socket to local port */
        ex(SRV, sa_addr_create(&saa_srv));
        ex(SRV, sa_addr_u2a(saa_srv, "inet://127.0.0.1:12345#tcp"));
        ex(SRV, sa_bind(sa_srv, saa_srv));
        ex(SRV, sa_addr_destroy(saa_srv));

        /* receive client connection */
        ex(SRV, sa_listen(sa_srv, 10));
        ex(SRV, sa_accept(sa_srv, &saa_cld, &sa_cld));

        /* communicate with client */
        ex(SRV, sa_writef(sa_cld, "%s", MSG_TCP_SRV_WELCOME));
        ex(SRV, sa_readln(sa_cld, buf_srv, sizeof(buf_srv), &l));
        if (strcmp(buf_srv, MSG_TCP_CLT_WELCOME) != 0)
            ts_test_fail(TS_CTX, "server: got \"%s\", expected \"%s\"",
                         buf_srv, MSG_TCP_CLT_WELCOME);
        ex(SRV, sa_writef(sa_cld, "%s", MSG_TCP_SRV_GOODBYE));
        ex(SRV, sa_shutdown(sa_cld, "w"));
        ex(SRV, sa_readln(sa_cld, buf_srv, sizeof(buf_srv), &l));
        if (strcmp(buf_srv, MSG_TCP_CLT_GOODBYE) != 0)
            ts_test_fail(TS_CTX, "server: got \"%s\", expected \"%s\"",
                         buf_srv, MSG_TCP_CLT_GOODBYE);
        ex(SRV, sa_shutdown(sa_cld, "r"));

        /* destroy client connection */
        ex(SRV, sa_destroy(sa_cld));
        ex(SRV, sa_addr_destroy(saa_cld));

        /* destroy server socket and die */
        ex(SRV, sa_destroy(sa_srv));
        exit(0);
    }
    else {
        /*
        ** CLIENT
        **/
        sleep(2);

        /* create client socket */
        ex(CLT, sa_create(&sa_clt));
        ex(CLT, sa_timeout(sa_clt, SA_TIMEOUT_ALL, 10, 0));

        /* connect to server */
        ex(CLT, sa_addr_create(&saa_clt));
        ex(CLT, sa_addr_u2a(saa_clt, "inet://127.0.0.1:12345#tcp"));
        ex(CLT, sa_connect(sa_clt, saa_clt));
        ex(CLT, sa_addr_destroy(saa_clt));

        /* communicate with server */
        ex(CLT, sa_readln(sa_clt, buf_clt, sizeof(buf_clt), &l));
        if (strcmp(buf_clt, MSG_TCP_SRV_WELCOME) != 0)
            ts_test_fail(TS_CTX, "client: got \"%s\", expected \"%s\"",
                         buf_clt, MSG_TCP_SRV_WELCOME);
        ex(CLT, sa_writef(sa_clt, "%s", MSG_TCP_CLT_WELCOME));
        ex(CLT, sa_readln(sa_clt, buf_clt, sizeof(buf_clt), &l));
        if (strcmp(buf_clt, MSG_TCP_SRV_GOODBYE) != 0)
            ts_test_fail(TS_CTX, "client: got \"%s\", expected \"%s\"",
                         buf_clt, MSG_TCP_SRV_GOODBYE);
        ex(CLT, sa_writef(sa_clt, "%s", MSG_TCP_CLT_GOODBYE));
        ex(CLT, sa_shutdown(sa_clt, "rw"));

        /* destroy server connection and wait for server to exit */
        ex(CLT, sa_destroy(sa_clt));
        waitpid(pid_srv, NULL, 0);
    }
}

/* test: client/server datagram communication */
TS_TEST(test_sa_datagram)
{
    sa_t *sa_clt;
    sa_t *sa_srv;
    sa_addr_t *saa_srv;
    sa_addr_t *saa_clt;
    pid_t pid_srv;
    char buf_srv[1024];
    char buf_clt[1024];
    size_t l;

#define MSG_UDP_CLT_REQUEST  "Who are you?\n"
#define MSG_UDP_SRV_RESPONSE "I'm god\n"

    if ((pid_srv = fork()) == 0) {
        /*
        ** SERVER
        */

        /* create server socket */
        ex(SRV, sa_create(&sa_srv));
        ex(SRV, sa_type(sa_srv, SA_TYPE_DATAGRAM));
        ex(SRV, sa_option(sa_srv, SA_OPTION_REUSEADDR, 1));
        ex(SRV, sa_option(sa_srv, SA_OPTION_REUSEPORT, 1));
        ex(SRV, sa_timeout(sa_srv, SA_TIMEOUT_ALL, 10, 0));

        /* bind socket to local port */
        ex(SRV, sa_addr_create(&saa_srv));
        ex(SRV, sa_addr_u2a(saa_srv, "inet://127.0.0.1:12345#udp"));
        ex(SRV, sa_bind(sa_srv, saa_srv));
        ex(SRV, sa_addr_destroy(saa_srv));

        /* communicate with client */
        ex(SRV, sa_recv(sa_srv, &saa_clt, buf_srv, sizeof(buf_srv), &l));
        if (strncmp(buf_srv, MSG_UDP_CLT_REQUEST, l) != 0)
            ts_test_fail(TS_CTX, "server: got \"%s\", expected \"%s\"",
                         buf_srv, MSG_UDP_CLT_REQUEST);
        ex(SRV, sa_sendf(sa_srv, saa_clt, "%s", MSG_UDP_SRV_RESPONSE));
        ex(SRV, sa_addr_destroy(saa_clt));

        /* destroy server socket and die */
        ex(SRV, sa_destroy(sa_srv));
        exit(0);
    }
    else {
        /*
        ** CLIENT
        **/
        sleep(2);

        /* create client socket */
        ex(CLT, sa_create(&sa_clt));
        ex(CLT, sa_type(sa_clt, SA_TYPE_DATAGRAM));
        ex(CLT, sa_timeout(sa_clt, SA_TIMEOUT_ALL, 10, 0));

        /* communicate with server */
        ex(CLT, sa_addr_create(&saa_srv));
        ex(CLT, sa_addr_u2a(saa_srv, "inet://127.0.0.1:12345#udp"));
        ex(CLT, sa_sendf(sa_clt, saa_srv, "%s", MSG_UDP_CLT_REQUEST));
        ex(CLT, sa_addr_destroy(saa_srv));
        ex(CLT, sa_recv(sa_clt, &saa_srv, buf_clt, sizeof(buf_clt), &l));
        if (strncmp(buf_clt, MSG_UDP_SRV_RESPONSE, l) != 0)
            ts_test_fail(TS_CTX, "client: got \"%s\", expected \"%s\"",
                         buf_clt, MSG_UDP_SRV_RESPONSE);
        ex(CLT, sa_addr_destroy(saa_srv));

        /* destroy server connection and wait for server to exit */
        ex(CLT, sa_destroy(sa_clt));
        waitpid(pid_srv, NULL, 0);
    }
}

/* test: exception handling */
#ifdef WITH_EX
#include "ex.h"
TS_TEST(test_sa_ex)
{
    sa_addr_t *saa;
    ex_t ex;
    int caught;

    ts_test_check(TS_CTX, "exception handling");
    caught = 0;
    ex_try {
        sa_addr_create(&saa);
        sa_addr_u2a(saa, "inet:DUMMY");
        sa_addr_destroy(saa);
    }
    ex_catch (ex) {
        if ((sa_rc_t)ex.ex_value != SA_ERR_ARG)
            ts_test_fail(TS_CTX, "unexpected exception: %d\n", (sa_rc_t)ex.ex_value);
        caught = 1;
    }
    if (!caught)
        ts_test_fail(TS_CTX, "expected exception not caught\n");
}
#endif

int main(int argc, char *argv[])
{
    ts_suite_t *ts;
    int n;

    ts = ts_suite_new("OSSP sa (Socket Abstraction)");
    ts_suite_test(ts, test_saa_impexp,  "socket address abstraction import/export");
    ts_suite_test(ts, test_saa_match,   "socket address abstraction matching");
    ts_suite_test(ts, test_sa_stream,   "socket abstraction stream communication");
    ts_suite_test(ts, test_sa_datagram, "socket abstraction datagram communication");
#ifdef WITH_EX
    ts_suite_test(ts, test_sa_ex,       "exception handling");
#endif
    n = ts_suite_run(ts);
    ts_suite_free(ts);
    return n;
}

