/*
**  SA - OSSP Socket Abstraction Library
**  Copyright (c) 2001 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
**  This file is part of OSSP SA, a socket abstraction library which
**  can be found at http://www.ossp.org/pkg/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.c: socket abstraction library
*/

/* include optional Autoconf header */
#include "l2_config.h"

/* include system API headers */
#include <stdio.h>       /* for "s[n]printf()" */
#include <stdlib.h>      /* for "malloc()" & friends */
#include <stdarg.h>      /* for "va_XXX()" and "va_list" */
#include <string.h>      /* for "strXXX()" and "size_t" */
#include <sys/types.h>   /* for general prerequisites */
#include <ctype.h>       /* for isXXX() */
#include <errno.h>       /* for "EXXX" */
#include <fcntl.h>       /* for "F_XXX" and "O_XXX" */
#include <unistd.h>      /* for standard Unix stuff */
#include <netdb.h>       /* for "struct prototent" */
#include <sys/time.h>    /* for "struct timeval" */
#include <sys/un.h>      /* for "struct sockaddr_un" */
#include <netinet/in.h>  /* for "struct sockaddr_in[6]" */
#include <sys/socket.h>  /* for "AF_XXX" and "SOCK_XXX" */
#include <arpa/inet.h>   /* for "inet_XtoX" */

/* include own API header */
#include "l2_ut_sa.h"

/* system call structure declaration macros */
#define SA_SC_DECLARE_0(rc_t, fn) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(void); \
                rc_t (*ctx)(void *); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_1(rc_t, fn, a1_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t); \
                rc_t (*ctx)(void *, a1_t); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_2(rc_t, fn, a1_t, a2_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t, a2_t); \
                rc_t (*ctx)(void *, a1_t, a2_t); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_3(rc_t, fn, a1_t, a2_t, a3_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t, a2_t, a3_t); \
                rc_t (*ctx)(void *, a1_t, a2_t, a3_t); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_4(rc_t, fn, a1_t, a2_t, a3_t, a4_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t, a2_t, a3_t, a4_t); \
                rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_5(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t); \
                rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t); } fptr; \
        void *fctx; \
    } sc_##fn;
#define SA_SC_DECLARE_6(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t) \
    struct { \
        union { void (*any)(); \
                rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); \
                rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); } fptr; \
        void *fctx; \
    } sc_##fn;

/* system call structure assignment macro */
#define SA_SC_ASSIGN(sa, fn, ptr, ctx) \
    do { \
        (sa)->scSysCall.sc_##fn.fptr.any = (void (*)())(ptr); \
        (sa)->scSysCall.sc_##fn.fctx = (ctx); \
    } while(0)

/* system call structure assignment macro */
#define SA_SC_COPY(sa1, sa2, fn) \
    do { \
        (sa1)->scSysCall.sc_##fn.fptr.any = (sa2)->scSysCall.sc_##fn.fptr.any; \
        (sa1)->scSysCall.sc_##fn.fctx     = (sa2)->scSysCall.sc_##fn.fctx; \
    } while(0)

/* system call function call macros */
#define SA_SC_CALL_0(sa, fn) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)() )
#define SA_SC_CALL_1(sa, fn, a1) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1) )
#define SA_SC_CALL_2(sa, fn, a1, a2) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2) )
#define SA_SC_CALL_3(sa, fn, a1, a2, a3) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3) )
#define SA_SC_CALL_4(sa, fn, a1, a2, a3, a4) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4) )
#define SA_SC_CALL_5(sa, fn, a1, a2, a3, a4, a5) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5) )
#define SA_SC_CALL_6(sa, fn, a1, a2, a3, a4, a5, a6) \
    (   (sa)->scSysCall.sc_##fn.fctx != NULL \
     ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5, a6) \
     : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5, a6) )

/* system call table */
typedef struct {
    SA_SC_DECLARE_3(int,     connect,  int, const struct sockaddr *, socklen_t)
    SA_SC_DECLARE_3(int,     accept,   int, struct sockaddr *, socklen_t *)
    SA_SC_DECLARE_5(int,     select,   int, fd_set *, fd_set *, fd_set *, struct timeval *)
    SA_SC_DECLARE_3(ssize_t, read,     int, void *, size_t)
    SA_SC_DECLARE_3(ssize_t, write,    int, const void *, size_t)
    SA_SC_DECLARE_6(ssize_t, recvfrom, int, void *, size_t, int, struct sockaddr *, socklen_t *)
    SA_SC_DECLARE_6(ssize_t, sendto,   int, const void *, size_t, int, const struct sockaddr *, socklen_t)
} sa_syscall_tab_t;
 
/* socket abstraction object */
struct sa_st {
    sa_type_t        eType;            /* socket type (stream or datagram) */
    int              fdSocket;         /* socket file descriptor */
    struct timeval   tvTimeout[4];     /* timeout values (sec, usec) */
    int              nReadLen;         /* read  buffer current length */
    int              nReadSize;        /* read  buffer current size */
    char            *cpReadBuf;        /* read  buffer memory chunk */
    int              nWriteLen;        /* write buffer current length */
    int              nWriteSize;       /* write buffer current size */
    char            *cpWriteBuf;       /* write buffer memory chunk */
    sa_syscall_tab_t scSysCall;        /* table of system calls */
};

/* socket address abstraction object */
struct sa_addr_st {
    int              nFamily;          /* the socket family (AF_XXX) */
    struct sockaddr *saBuf;            /* the "struct sockaddr_xx" actually */
    socklen_t        slBuf;            /* the length of "struct sockaddr_xx" */
};

/* boolean values */
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE  (!FALSE)
#endif

/* handy struct timeval check */
#define SA_TVISZERO(tv) \
    ((tv).tv_sec == 0 && (tv).tv_usec == 0)

/* backward compatibility for AF_LOCAL */
#if !defined(AF_LOCAL) && defined(AF_UNIX)
#define AF_LOCAL AF_UNIX
#endif

/* convert Internet address from presentation to network format */
static int sa_inet_pton(int family, const char *strptr, void *addrptr)
{
#ifdef HAVE_INET_PTON
    return inet_pton(family, strptr, addrptr);
#else
    struct in_addr in_val;

    if (family == AF_INET) {
        /* at least for IPv4 we can rely on the old inet_aton(3)
           and for IPv6 inet_pton(3) would exist anyway */
        if (inet_aton(strptr, &in_val) == 0)
            return 0;
        memcpy(addrptr, &in_val, sizeof(struct in_addr));
        return 1;
    }
    errno = EAFNOSUPPORT;
    return 0;
#endif
}

/* convert Internet address from network to presentation format */
static const char *sa_inet_ntop(int family, const void *src, char *dst, size_t size)
{
#ifdef HAVE_INET_NTOP
    return inet_ntop(family, src, dst, size);
#else
    struct in_addr in_val;
    char *cp;
    int n;

    if (family == AF_INET) {
        /* at least for IPv4 we can rely on the old inet_ntoa(3)
           and for IPv6 inet_ntop(3) would exist anyway */
        if ((cp = inet_ntoa(src)) == NULL) 
            return NULL;
        n = strlen(cp);
        if (n > size-1)
            n = size-1;
        memcpy(dst, cp, n);
        dst[n] = '\0';
        return dst;
    }
    errno = EAFNOSUPPORT;
    return NULL;
#endif
}

/* minimal output-independent vprintf(3) variant which supports %{c,s,d,%} only */
static int sa_mvxprintf(int (*output)(void *ctx, const char *buffer, size_t bufsize), void *ctx, const char *format, va_list ap)
{
    /* sufficient integer buffer: <available-bits> x log_10(2) + safety */
    char ibuf[((sizeof(int)*8)/3)+10]; 
    char *cp;
    char c;
    int d;
    int n;
    int bytes;

    if (format == NULL || ap == NULL)
        return -1;
    bytes = 0;
    while (*format != '\0') {
        if (*format == '%') {
            c = *(format+1);
            if (c == '%') {
                /* expand "%%" */
                cp = &c;
                n = sizeof(char);
            }
            else if (c == 'c') {
                /* expand "%c" */
                c = (char)va_arg(ap, int);
                cp = &c;
                n = sizeof(char);
            }
            else if (c == 's') {
                /* expand "%s" */
                if ((cp = (char *)va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                n = strlen(cp);
            }
            else if (c == 'd') {
                /* expand "%d" */
                d = (int)va_arg(ap, int);
#ifdef HAVE_SNPRINTF
                snprintf(ibuf, sizeof(ibuf), "%d", d); /* explicitly secure */
#else
                sprintf(ibuf, "%d", d);                /* implicitly secure */
#endif
                cp = ibuf;
                n = strlen(cp);
            }
            else {
                /* any other "%X" */
                cp = (char *)format;
                n  = 2;
            }
            format += 2;
        }
        else {
            /* plain text */
            cp = (char *)format;
            if ((format = strchr(cp, '%')) == NULL)
                format = strchr(cp, '\0');
            n = format - cp;
        }
        /* perform output operation */
        if (output != NULL)
            if ((n = output(ctx, cp, n)) == -1)
                break;
        bytes += n;
    }
    return bytes;
}

/* output callback function context for sa_mvsnprintf() */
typedef struct {
    char *bufptr;
    size_t buflen;
} sa_mvsnprintf_cb_t;

/* output callback function for sa_mvsnprintf() */
static int sa_mvsnprintf_cb(void *_ctx, const char *buffer, size_t bufsize)
{
    sa_mvsnprintf_cb_t *ctx = (sa_mvsnprintf_cb_t *)_ctx;

    if (bufsize > ctx->buflen)
        return -1;
    memcpy(ctx->bufptr, buffer, bufsize);
    ctx->bufptr += bufsize;
    ctx->buflen -= bufsize;
    return bufsize;
}

/* minimal vsnprintf(3) variant which supports %{c,s,d} only */
static int sa_mvsnprintf(char *buffer, size_t bufsize, const char *format, va_list ap)
{
    int n;
    sa_mvsnprintf_cb_t ctx;

    if (format == NULL || ap == NULL)
        return -1;
    if (buffer != NULL && bufsize == 0)
        return -1;
    if (buffer == NULL) 
        /* just determine output length */
        n = sa_mvxprintf(NULL, NULL, format, ap);
    else {
        /* perform real output */
        ctx.bufptr = buffer;
        ctx.buflen = bufsize - 1;
        n = sa_mvxprintf(sa_mvsnprintf_cb, &ctx, format, ap);
    }
    if (n != -1 && ctx.buflen == 0)
        n = -1;
    if (n != -1)
        *(ctx.bufptr) = '\0';
    return n;
}

/* minimal snprintf(3) variant which supports %{c,s,d} only */
static int sa_msnprintf(char *buffer, size_t bufsize, const char *format, ...)
{
    int chars;
    va_list ap;

    /* pass through to va_list based variant */
    va_start(ap, format);
    chars = sa_mvsnprintf(buffer, bufsize, format, ap);
    va_end(ap);

    return chars;
}

/* create address object */
sa_rc_t sa_addr_create(sa_addr_t **saap)
{
    sa_addr_t *saa;

    /* argument sanity check(s) */
    if (saap == NULL)
        return SA_ERR_ARG;

    /* allocate and initialize new address object */
    if ((saa = (sa_addr_t *)malloc(sizeof(sa_addr_t))) == NULL)
        return SA_ERR_MEM;
    saa->nFamily = 0;
    saa->saBuf   = NULL;
    saa->slBuf   = 0;

    /* pass object to caller */
    *saap = saa;

    return SA_OK;
}

/* destroy address object */
sa_rc_t sa_addr_destroy(sa_addr_t *saa)
{
    /* argument sanity check(s) */
    if (saa == NULL)
        return SA_ERR_ARG;

    /* free address objects and sub-object(s) */
    if (saa->saBuf != NULL)
        free(saa->saBuf);
    free(saa);

    return SA_OK;
}

/* import URI into address object */
sa_rc_t sa_addr_u2a(sa_addr_t *saa, const char *uri, ...)
{
    va_list ap;
    int sf;
    socklen_t sl;
    struct sockaddr *sa;
    struct sockaddr_un un;
    struct sockaddr_in sa4;
#ifdef AF_INET6
    struct sockaddr_in6 sa6;
#endif
    struct hostent *he;
    struct servent *se;
    int bNumeric;
    char *cpHost;
    char *cpPort;
    char *cpProto;
    int nPort;
    const char *cpPath;
    char uribuf[1024];
    char *cp;
    int i;
    int n;

    /* argument sanity check(s) */
    if (saa == NULL || uri == NULL)
        return SA_ERR_ARG;

    /* on-the-fly create or just take over URI */
    va_start(ap, uri);
    sa_mvsnprintf(uribuf, sizeof(uribuf), uri, ap);
    va_end(ap);

    /* initialize result variables */
    sa = NULL;
    sl = 0;
    sf = 0;

    /* parse URI and resolve contents. 
       The following syntax is recognized:
       - unix:<path>
       - inet://<host>:<port>[#(tcp|udp)] */
    uri = uribuf;
    if (strncmp(uri, "unix:", 5) == 0) {
        /* parse URI */
        cpPath = uri+5;

        /* mandatory(!) socket address structure initialization */
        memset(&un, 0, sizeof(un));

        /* fill-in socket address structure */
        n = strlen(cpPath);
        if (n == 0)
            return SA_ERR_ARG;
        if ((n+1) > sizeof(un.sun_path))
            return SA_ERR_MEM;
        if (cpPath[0] != '/') {
            if (getcwd(un.sun_path, sizeof(un.sun_path)-(n+1)) == NULL)
                return SA_ERR_MEM;
            cp = un.sun_path + strlen(un.sun_path);
        }
        else
            cp = un.sun_path;
        memcpy(un.sun_path, cpPath, n+1);
        un.sun_family = AF_LOCAL;

        /* provide results */
        sa = (struct sockaddr *)&un;
        sl = sizeof(un);
        sf = AF_LOCAL;
    }
    else if (strncmp(uri, "inet://", 7) == 0) {
        /* parse URI */
        cpHost = (char *)(uri+7);
        if ((cp = strrchr(cpHost, ':')) == NULL)
            return SA_ERR_ARG;
        *cp++ = '\0';
        cpPort = cp;

        /* resolve port */
        nPort = 0;
        bNumeric = 1;
        for (i = 0; cpPort[i] != '\0'; i++) {
            if (!isdigit((int)cpPort[i])) {
                bNumeric = 0;
                break;
            }
        }
        if (bNumeric)
            nPort = atoi(cpPort);
        else {
            cpProto = "tcp";
            if ((cp = strchr(cpPort, '#')) != NULL) {
                *cp++ = '\0';
                cpProto = cp;
            }
            if ((se = getservbyname(cpPort, cpProto)) == NULL)
                return SA_ERR_SYS;
            nPort = ntohs(se->s_port);
        }

        /* mandatory(!) socket address structure initialization */
        memset(&sa4, 0, sizeof(sa4));
#ifdef AF_INET6
        memset(&sa6, 0, sizeof(sa6));
#endif

        /* resolve host by trying to parse it as either directly a IPv4 or
           IPv6 address or by resolving it to either a IPv4 or IPv6 address */
        if (sa_inet_pton(AF_INET, cpHost, &sa4.sin_addr.s_addr) == 1) {
            sa4.sin_family = AF_INET;
            sa4.sin_port = htons(nPort);
            sa = (struct sockaddr *)&sa4;
            sl = sizeof(sa4);
            sf = AF_INET;
        }
#ifdef AF_INET6
        else if (sa_inet_pton(AF_INET6, cpHost, &sa6.sin6_addr.s6_addr) == 1) {
            sa6.sin6_family = AF_INET6;
            sa6.sin6_port = htons(nPort);
            sa = (struct sockaddr *)&sa6;
            sl = sizeof(sa6);
            sf = AF_INET6;
        }
#endif
        else if ((he = gethostbyname(cpHost)) != NULL) {
            if (he->h_addrtype == AF_INET) {
                sa4.sin_family = AF_INET;
                sa4.sin_port = htons(nPort);
                memcpy(&sa4.sin_addr.s_addr, he->h_addr_list[0],
                       sizeof(sa4.sin_addr.s_addr));
                sa = (struct sockaddr *)&sa4;
                sl = sizeof(sa4);
                sf = AF_INET;
            }
#ifdef AF_INET6
            else if (he->h_addrtype == AF_INET6) {
                sa6.sin6_family = AF_INET6;
                sa6.sin6_port = htons(nPort);
                memcpy(&sa6.sin6_addr.s6_addr, he->h_addr_list[0], 
                       sizeof(sa6.sin6_addr.s6_addr));
                sa = (struct sockaddr *)&sa6;
                sl = sizeof(sa6);
                sf = AF_INET6;
            }
#endif
            else
                return SA_ERR_ARG;
        }
        else
            return SA_ERR_ARG;
    }
    else
        return SA_ERR_ARG;

    /* fill-in result address structure */
    if (saa->saBuf != NULL)
        free(saa->saBuf);
    if ((saa->saBuf = (struct sockaddr *)malloc(sl)) == NULL)
        return SA_ERR_MEM;
    memcpy(saa->saBuf, sa, sl);
    saa->slBuf = sl;
    saa->nFamily = sf;

    return SA_OK;
}

/* import "struct sockaddr" into address object */
sa_rc_t sa_addr_s2a(sa_addr_t *saa, const struct sockaddr *sabuf, socklen_t salen)
{
    struct sockaddr_un *un;
    struct sockaddr_in *sa4;
#ifdef AF_INET6
    struct sockaddr_in6 *sa6;
#endif
    int sf;

    /* argument sanity check(s) */
    if (saa == NULL || sabuf == NULL || salen == 0)
        return SA_ERR_ARG;

    /* create result address structure */
    if (saa->saBuf != NULL)
        free(saa->saBuf);
    if ((saa->saBuf = (struct sockaddr *)malloc(salen)) == NULL)
        return SA_ERR_MEM;
    memcpy(saa->saBuf, sabuf, salen);
    saa->slBuf = salen;

    /* resolve family */
    sf = 0;
    if (sizeof(struct sockaddr_un) == salen) {
        un = (struct sockaddr_un *)((void *)sabuf);
        if (un->sun_family == AF_LOCAL)
            sf = AF_LOCAL;
    }
    if (sf == 0 && sizeof(struct sockaddr_in) == salen) {
        sa4 = (struct sockaddr_in *)((void *)sabuf);
        if (sa4->sin_family == AF_INET)
            sf = AF_INET;
    }
#ifdef AF_INET6
    if (sf == 0 && sizeof(struct sockaddr_in6) == salen) {
        sa6 = (struct sockaddr_in6 *)((void *)sabuf);
        if (sa6->sin6_family == AF_INET6)
            sf = AF_INET6;
    }
#endif
    if (sf == 0)
        return SA_ERR_ARG;
    saa->nFamily = sf;

    return SA_OK;
}

/* export address object into URI */
sa_rc_t sa_addr_a2u(sa_addr_t *saa, char **uri)
{
    char uribuf[1024];
    struct sockaddr_un *un;
    struct sockaddr_in *sa4;
#ifdef AF_INET6
    struct sockaddr_in6 *sa6;
#endif
    char caHost[512];
    int nPort;

    /* argument sanity check(s) */
    if (saa == NULL || uri == NULL)
        return SA_ERR_ARG;

    /* export object contents */
    if (saa->nFamily == AF_LOCAL) {
        un = (struct sockaddr_un *)((void *)saa->saBuf);
        sa_msnprintf(uribuf, sizeof(uribuf), "unix:%s", un->sun_path);
    }
#ifdef AF_INET6
    else if (saa->nFamily == AF_INET || saa->nFamily == AF_INET6) {
#else
    else if (saa->nFamily == AF_INET) {
#endif
        if (saa->nFamily == AF_INET) {
            sa4 = (struct sockaddr_in *)((void *)saa->saBuf);
            sa_inet_ntop(AF_INET, &sa4->sin_addr.s_addr, caHost, sizeof(caHost));
            nPort = ntohs(sa4->sin_port);
        }
#ifdef AF_INET6
        else {
            sa6 = (struct sockaddr_in6 *)((void *)saa->saBuf);
            sa_inet_ntop(AF_INET6, &sa6->sin6_addr.s6_addr, caHost, sizeof(caHost));
            nPort = ntohs(sa6->sin6_port);
        }
#endif
        sa_msnprintf(uribuf, sizeof(uribuf), "inet://%s:%d", caHost, nPort);
    }
    else
        return SA_ERR_INT;

    /* pass result to caller */
    *uri = strdup(uribuf);

    return SA_OK;
}

/* export address object into "struct sockaddr" */
sa_rc_t sa_addr_a2s(sa_addr_t *saa, struct sockaddr **sabuf, socklen_t *salen)
{
    /* argument sanity check(s) */
    if (saa == NULL || sabuf == NULL || salen == 0)
        return SA_ERR_ARG;

    /* export underlying address structure */
    if ((*sabuf = (struct sockaddr *)malloc(saa->slBuf)) == NULL)
        return SA_ERR_MEM;
    memmove(*sabuf, saa->saBuf, saa->slBuf);
    *salen = saa->slBuf;

    return SA_OK;
}

sa_rc_t sa_addr_match(sa_addr_t *saa1, sa_addr_t *saa2, int prefixlen)
{
    const unsigned char *ucp1, *ucp2;
    unsigned int uc1, uc2, mask;
    size_t l1, l2;
    int nFamily;
    int nBytes;
    int nBits;

    /* argument sanity check(s) */
    if (saa1 == NULL || saa2 == NULL || prefixlen < -1)
        return SA_ERR_ARG;

    /* match short circuiting */
    if (saa1->nFamily != saa2->nFamily)
        return SA_ERR_MTC;

    /* determine address representation pointer and size */
    nFamily = saa1->nFamily;
    if (nFamily == AF_LOCAL) {
        ucp1 = (const unsigned char *)(((struct sockaddr_un *)saa1->saBuf)->sun_path);
        ucp2 = (const unsigned char *)(((struct sockaddr_un *)saa2->saBuf)->sun_path);
        l1 = strlen(((struct sockaddr_un *)saa1->saBuf)->sun_path) * 8;
        l2 = strlen(((struct sockaddr_un *)saa2->saBuf)->sun_path) * 8;
        if (prefixlen == -1) {
            if (l1 != l2)
                return SA_ERR_MTC;
            nBits = l1;
        }
        else {
            if (l1 < prefixlen || l2 < prefixlen)
                return SA_ERR_MTC;
            nBits = prefixlen;
        }
    }
    else if (nFamily == AF_INET) {
        ucp1 = (const unsigned char *)&(((struct sockaddr_in *)saa1->saBuf)->sin_addr);
        ucp2 = (const unsigned char *)&(((struct sockaddr_in *)saa2->saBuf)->sin_addr);
        nBits = 32;
    }
#ifdef AF_INET6
    else if (nFamily == AF_INET6) {
        ucp1 = (const unsigned char *)&(((struct sockaddr_in6 *)saa1->saBuf)->sin6_addr);
        ucp2 = (const unsigned char *)&(((struct sockaddr_in6 *)saa2->saBuf)->sin6_addr);
        nBits = 128;
#endif
    }
    else
        return SA_ERR_INT;

    /* make sure we do not compare than possible */
    if (prefixlen > nBits)
        return SA_ERR_ARG;

    /* support equal matching (= all bits) */
    if (prefixlen == -1)
        prefixlen = nBits;

    /* perform address representation comparison 
       (assumption guaranteed by API: network byte order is used) */
    nBytes = (prefixlen / 8);
    nBits  = (prefixlen % 8);
    if (nBytes > 0) {
        if (memcmp(ucp1, ucp2, nBytes) != 0)
            return SA_ERR_MTC;
    }
    if (nBits > 0) {
        uc1 = ucp1[nBytes];
        uc2 = ucp2[nBytes];
        mask = (0xFF << (8-nBits)) & 0xFF;
        if ((uc1 & mask) != (uc2 & mask))
            return SA_ERR_MTC;
    }
    return SA_OK;
}

/* set timeouts if timeouts or done in kernel */
static sa_rc_t sa_socket_settimeouts(sa_t *sa)
{
#if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)
    if (sa->fdSocket != -1) {
        if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
            if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_RCVTIMEO,
                           &sa->tvTimeout[SA_TIMEOUT_READ],
                           sizeof(sa->tvTimeout[SA_TIMEOUT_READ])) < 0)
                return SA_ERR_SYS;
        }
        if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
            if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_SNDTIMEO,
                           &sa->tvTimeout[SA_TIMEOUT_WRITE],
                           sizeof(sa->tvTimeout[SA_TIMEOUT_WRITE])) < 0)
                return SA_ERR_SYS;
        }
    }
#endif
    return SA_OK;
}

/* internal lazy/delayed initialization of underlying socket */
static sa_rc_t sa_socket_init(sa_t *sa, int nFamily)
{
    int nType;
    int nProto;
    struct protoent *pe;
    char *cpProto;

    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    /* only perform operation if socket still does not exist */
    if (sa->fdSocket != -1)
        return SA_ERR_USE;

    /* determine socket type */
    if (sa->eType == SA_TYPE_STREAM)
        nType = SOCK_STREAM;
    else if (sa->eType == SA_TYPE_DATAGRAM)
        nType = SOCK_DGRAM;
    else
        return SA_ERR_INT;

    /* determine socket protocol */
    if (nFamily == AF_LOCAL) 
        nProto = 0;
#ifdef AF_INET6
    else if (nFamily == AF_INET || nFamily == AF_INET6) {
#else
    else if (nFamily == AF_INET) {
#endif
        if (nType == SOCK_STREAM)
            cpProto = "tcp";
        else if (nType == SOCK_DGRAM)
            cpProto = "udp";
        else
            return SA_ERR_INT;
        if ((pe = getprotobyname(cpProto)) == NULL)
            return SA_ERR_SYS;
        nProto = pe->p_proto;
    }
    else
        return SA_ERR_INT;

    /* create the underlying socket */
    if ((sa->fdSocket = socket(nFamily, nType, nProto)) == -1)
        return SA_ERR_SYS;

    /* optionally set kernel timeouts */
    sa_socket_settimeouts(sa);

    return SA_OK;
}

/* internal destruction of underlying socket */
static sa_rc_t sa_socket_kill(sa_t *sa)
{
    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    /* check context */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* close socket */
    close(sa->fdSocket);
    sa->fdSocket = -1;

    return SA_OK;
}

/* create abstract socket object */
sa_rc_t sa_create(sa_t **sap)
{
    sa_t *sa;
    int i;

    /* argument sanity check(s) */
    if (sap == NULL)
        return SA_ERR_ARG;

    /* allocate and initialize socket object */
    if ((sa = (sa_t *)malloc(sizeof(sa_t))) == NULL)
        return SA_ERR_MEM;

    /* init object attributes */
    sa->eType          = SA_TYPE_STREAM;
    sa->fdSocket       = -1;
    sa->nReadLen       = 0;
    sa->nReadSize      = 0;
    sa->cpReadBuf      = NULL;
    sa->nWriteLen      = 0;
    sa->nWriteSize     = 0;
    sa->cpWriteBuf     = NULL;

    /* init timeval object attributes */
    for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
        sa->tvTimeout[i].tv_sec  = 0;
        sa->tvTimeout[i].tv_usec = 0;
    }

    /* init syscall object attributes */
    SA_SC_ASSIGN(sa, connect,  connect,  NULL);
    SA_SC_ASSIGN(sa, accept,   accept,   NULL);
    SA_SC_ASSIGN(sa, select,   select,   NULL);
    SA_SC_ASSIGN(sa, read,     read,     NULL);
    SA_SC_ASSIGN(sa, write,    write,    NULL);
    SA_SC_ASSIGN(sa, recvfrom, recvfrom, NULL);
    SA_SC_ASSIGN(sa, sendto,   sendto,   NULL);

    /* pass object to caller */
    *sap = sa;

    return SA_OK;
}

/* destroy abstract socket object */
sa_rc_t sa_destroy(sa_t *sa)
{
    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    /* kill underlying socket */
    sa_socket_kill(sa);

    /* free object and sub-objects */
    if (sa->cpReadBuf != NULL)
        free(sa->cpReadBuf);
    if (sa->cpWriteBuf != NULL)
        free(sa->cpWriteBuf);
    free(sa);

    return SA_OK;
}

/* switch communication type of socket */
sa_rc_t sa_type(sa_t *sa, sa_type_t type)
{
    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;
    if (!(type == SA_TYPE_STREAM || type == SA_TYPE_DATAGRAM))
        return SA_ERR_ARG;

    /* kill underlying socket if type changes */
    if (sa->eType != type)
        sa_socket_kill(sa);

    /* set new type */
    sa->eType = type;

    return SA_OK;
}

/* configure I/O timeout */
sa_rc_t sa_timeout(sa_t *sa, sa_timeout_t id, long sec, long usec)
{
    int i;

    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    if (id == SA_TIMEOUT_ALL) {
        for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
            sa->tvTimeout[i].tv_sec  = sec;
            sa->tvTimeout[i].tv_usec = usec;
        }
    }
    else {
        sa->tvTimeout[id].tv_sec  = sec;
        sa->tvTimeout[id].tv_usec = usec;
    }

    /* optionally set kernel timeouts */
    sa_socket_settimeouts(sa);

    return SA_OK;
}

/* configure I/O buffers */
sa_rc_t sa_buffer(sa_t *sa, sa_buffer_t id, size_t size)
{
    char *cp;

    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    if (id == SA_BUFFER_READ) {
        /* configure read/incoming buffer */
        if (sa->nReadLen > size)
            return SA_ERR_USE;
        if (size > 0) {
            if (sa->cpReadBuf == NULL)
                cp = (char *)malloc(size);
            else
                cp = (char *)realloc(sa->cpReadBuf, size);
            if (cp == NULL)
                return SA_ERR_SYS;
            sa->cpReadBuf = cp;
            sa->nReadSize = size;
        }
        else {
            if (sa->cpReadBuf != NULL)
                free(sa->cpReadBuf);
            sa->cpReadBuf = NULL;
            sa->nReadSize = 0;
        }
    }
    else if (id == SA_BUFFER_WRITE) {
        /* configure write/outgoing buffer */
        if (sa->nWriteLen > size)
            return SA_ERR_USE;
        if (size > 0) {
            if (sa->cpWriteBuf == NULL)
                cp = (char *)malloc(size);
            else
                cp = (char *)realloc(sa->cpWriteBuf, size);
            if (cp == NULL)
                return SA_ERR_SYS;
            sa->cpWriteBuf = cp;
            sa->nWriteSize = size;
        }
        else {
            if (sa->cpWriteBuf != NULL)
                free(sa->cpWriteBuf);
            sa->cpWriteBuf = NULL;
            sa->nWriteSize = 0;
        }
    }
        return SA_ERR_ARG;

    return SA_OK;
}

/* override system call */
sa_rc_t sa_syscall(sa_t *sa, sa_syscall_t id, void (*fptr)(), void *fctx)
{
    if (sa == NULL || fptr == NULL)
        return SA_ERR_ARG;
    if (id == SA_SYSCALL_CONNECT)
        SA_SC_ASSIGN(sa, connect, fptr, fctx); 
    else if (id == SA_SYSCALL_ACCEPT)
        SA_SC_ASSIGN(sa, accept, fptr, fctx); 
    else if (id == SA_SYSCALL_SELECT)
        SA_SC_ASSIGN(sa, select, fptr, fctx); 
    else if (id == SA_SYSCALL_READ)
        SA_SC_ASSIGN(sa, read, fptr, fctx); 
    else if (id == SA_SYSCALL_WRITE)
        SA_SC_ASSIGN(sa, write, fptr, fctx); 
    else if (id == SA_SYSCALL_RECVFROM)
        SA_SC_ASSIGN(sa, recvfrom, fptr, fctx); 
    else if (id == SA_SYSCALL_SENDTO)
        SA_SC_ASSIGN(sa, sendto, fptr, fctx); 
    else
        return SA_ERR_ARG;
    return SA_OK;
}

/* bind socket to a local address */
sa_rc_t sa_bind(sa_t *sa, sa_addr_t *laddr)
{
    sa_rc_t rv;
    struct sockaddr_un *un;

    /* argument sanity check(s) */
    if (sa == NULL || laddr == NULL)
        return SA_ERR_ARG;

    /* lazy creation of underlying socket */
    if (sa->fdSocket == -1)
        if ((rv = sa_socket_init(sa, laddr->nFamily)) != SA_OK)
            return rv;

    /* remove a possibly existing old Unix Domain socket on filesystem */
    if (laddr->nFamily == AF_LOCAL) {
        un = (struct sockaddr_un *)((void *)laddr->saBuf);
        unlink(un->sun_path);
    }

    /* perform bind operation on underlying socket */
    if (bind(sa->fdSocket, laddr->saBuf, laddr->slBuf) == -1)
        return SA_ERR_SYS;

    return SA_OK;
}

/* connect socket to a remote address */
sa_rc_t sa_connect(sa_t *sa, sa_addr_t *raddr)
{
    int flags, n, error;
    fd_set rset, wset;
    socklen_t len;
    sa_rc_t rv;

    /* argument sanity check(s) */
    if (sa == NULL || raddr == NULL)
        return SA_ERR_ARG;

    /* connecting is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* lazy creation of underlying socket */
    if (sa->fdSocket == -1)
        if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK)
            return rv;

    rv = SA_OK;
    if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_CONNECT])) {
        /* standard/non-timeout-aware connect operation */
        if (SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf) < 0)
            rv = SA_ERR_SYS;
    }
    else {
        /* emulated/timeout-aware connect operation */
        error = 0;

        /* temporarily switch underlying socket to non-blocking mode */
        flags = fcntl(sa->fdSocket, F_GETFL, 0);
        fcntl(sa->fdSocket, F_SETFL, flags|O_NONBLOCK);

        /* perform the connect operation */
        if ((n = SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf)) < 0) {
            if (errno != EINPROGRESS) {
                error = errno;
                goto done;
            }
        }

        /* ok if connect completed immediately */
        if (n == 0)
            goto done; 

        /* wait for read or write possibility */
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        FD_SET(sa->fdSocket, &rset);
        FD_SET(sa->fdSocket, &wset);
        do {
            n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &rset, &wset, NULL,
                             &sa->tvTimeout[SA_TIMEOUT_CONNECT]);
        } while (n == -1 && errno == EINTR);

        /* decide on return semantic */
        if (n < 0) {
            error = errno;
            goto done;
        }
        else if (n == 0) {
            close(sa->fdSocket); /* stop TCP three-way handshake */
            rv = SA_ERR_TMT;
            goto done;
        }

        /* fetch pending error */
        len = sizeof(error);
        if (getsockopt(sa->fdSocket, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
            error = errno;

        done:

        /* reset socket flags */
        fcntl(sa->fdSocket, F_SETFL, flags);

        /* optionally set errno */
        if (error != 0) {
            close(sa->fdSocket); /* just in case */
            errno = error;
            rv = SA_ERR_SYS;
        }
    }
    return rv;
}

/* listen on socket for connections */
sa_rc_t sa_listen(sa_t *sa, int backlog)
{
    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    /* listening is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least sa_bind() has to be already performed */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* perform listen operation on underlying socket */
    if (listen(sa->fdSocket, backlog) == -1)
        return SA_ERR_SYS;

    return SA_OK;
}

/* accept a connection on socket */
sa_rc_t sa_accept(sa_t *sa, sa_addr_t **caddr, sa_t **csa)
{
    sa_rc_t rv;
    int n;
    fd_set fds;
    union {
        struct sockaddr_in sa4;
#ifdef AF_INET6
        struct sockaddr_in6 sa6;
#endif
    } sa_buf;
    socklen_t sa_len;
    int s;
    int i;

    /* argument sanity check(s) */
    if (sa == NULL || caddr == NULL || csa == NULL)
        return SA_ERR_ARG;

    /* accepting connections is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least sa_listen() has to be already performed */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* if timeout is enabled, perform a smart-blocking wait */
    if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_ACCEPT])) {
        FD_ZERO(&fds);
        FD_SET(sa->fdSocket, &fds);
        do {
            n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL,
                             &sa->tvTimeout[SA_TIMEOUT_ACCEPT]);
        } while (n == -1 && errno == EINTR);
        if (n == 0) 
            return SA_ERR_TMT;
        if (n <= 0)
            return SA_ERR_SYS;
    }

    /* perform accept operation on underlying socket */
    sa_len = sizeof(sa_buf);
    if ((s = SA_SC_CALL_3(sa, accept, sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_len)) == -1)
        return SA_ERR_SYS;

    /* create result address object */
    if ((rv = sa_addr_create(caddr)) != SA_OK)
        return rv;
    if ((rv = sa_addr_s2a(*caddr, (struct sockaddr *)&sa_buf, sa_len)) != SA_OK) {
        sa_addr_destroy(*caddr);
        return rv;
    }

    /* create result socket object */
    if ((rv = sa_create(csa)) != SA_OK) {
        sa_addr_destroy(*caddr);
        return rv;
    }

    /* fill-in child socket */
    (*csa)->fdSocket = s;

    /* copy-over original system calls */
    SA_SC_COPY((*csa), sa, connect);
    SA_SC_COPY((*csa), sa, accept);
    SA_SC_COPY((*csa), sa, select);
    SA_SC_COPY((*csa), sa, read);
    SA_SC_COPY((*csa), sa, write);
    SA_SC_COPY((*csa), sa, recvfrom);
    SA_SC_COPY((*csa), sa, sendto);

    /* copy-over original timeout values */
    for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) {
        (*csa)->tvTimeout[i].tv_sec  = sa->tvTimeout[i].tv_sec;
        (*csa)->tvTimeout[i].tv_usec = sa->tvTimeout[i].tv_usec;
    }

    return SA_OK;
}

/* determine remote address */
sa_rc_t sa_getremote(sa_t *sa, sa_addr_t **raddr)
{
    sa_rc_t rv;
    union {
        struct sockaddr_in sa4;
#ifdef AF_INET6
        struct sockaddr_in6 sa6;
#endif
    } sa_buf;
    socklen_t sa_len;

    /* argument sanity check(s) */
    if (sa == NULL || raddr == NULL)
        return SA_ERR_ARG;

    /* peers exist only for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least sa_connect() or sa_accept() has to be already performed */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* determine remote address of underlying socket */
    sa_len = sizeof(sa_buf);
    if (getpeername(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_len) < 0)
        return SA_ERR_SYS;

    /* create result address object */
    if ((rv = sa_addr_create(raddr)) != SA_OK)
        return rv;
    if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_len)) != SA_OK) {
        sa_addr_destroy(*raddr);
        return rv;
    }

    return SA_OK;
}

/* determine local address */
sa_rc_t sa_getlocal(sa_t *sa, sa_addr_t **laddr)
{
    sa_rc_t rv;
    union {
        struct sockaddr_in sa4;
#ifdef AF_INET6
        struct sockaddr_in6 sa6;
#endif
    } sa_buf;
    socklen_t sa_len;

    /* argument sanity check(s) */
    if (sa == NULL || laddr == NULL)
        return SA_ERR_ARG;

    /* at least sa_bind() has to be already performed */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* determine local address of underlying socket */
    sa_len = sizeof(sa_buf);
    if (getsockname(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_len) < 0)
        return SA_ERR_SYS;

    /* create result address object */
    if ((rv = sa_addr_create(laddr)) != SA_OK)
        return rv;
    if ((rv = sa_addr_s2a(*laddr, (struct sockaddr *)&sa_buf, sa_len)) != SA_OK) {
        sa_addr_destroy(*laddr);
        return rv;
    }

    return SA_OK;
}

/* peek at underlying socket */
sa_rc_t sa_getfd(sa_t *sa, int *fd)
{
    /* argument sanity check(s) */
    if (sa == NULL || fd == NULL)
        return SA_ERR_ARG;

    /* if still no socket exists, say this explicitly */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* pass socket to caller */
    *fd = sa->fdSocket;

    return SA_OK;
}

/* internal raw read operation */
static int sa_read_raw(sa_t *sa, char *cpBuf, int nBufLen)
{
    int rv;
#if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO))
    fd_set fds;
#endif

    /* if timeout is enabled, perform explicit/smart blocking instead 
       of implicitly/hard blocking in the read(2) system call */
#if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO))
    if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
        FD_ZERO(&fds);
        FD_SET(sa->fdSocket, &fds);
        do {
            rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL, 
                              &sa->tvTimeout[SA_TIMEOUT_READ]);
        } while (rv == -1 && errno == EINTR);
        if (rv == 0) {
            errno = ETIMEDOUT;
            return -1;
        }
    }
#endif

    /* perform read operation on underlying socket */
    do {
        rv = SA_SC_CALL_3(sa, read, sa->fdSocket, cpBuf, nBufLen);
    } while (rv == -1 && errno == EINTR);

#if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)
    if (rv == -1 && errno == EWOULDBLOCK)
        errno = ETIMEDOUT;
#endif

    return rv;
}

/* read data from socket */
sa_rc_t sa_read(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
    int n;
    sa_rc_t rv;
    size_t res;

    /* argument sanity check(s) */
    if (sa == NULL || cpBuf == NULL || nBufReq == 0)
        return SA_ERR_ARG;

    /* reading is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* trigger a write buffer flush */
    if (sa->nWriteLen > 0)
        sa_flush(sa);

    /* perform read operation */
    rv = SA_OK;
    if (sa->nReadSize == 0) {
        /* user-space unbuffered I/O */
        res = sa_read_raw(sa, cpBuf, nBufReq);
        if (res == 0)
            rv = SA_ERR_EOF;
        else if (res < 0 && errno == ETIMEDOUT)
            rv = SA_ERR_TMT;
        else if (res < 0)
            rv = SA_ERR_SYS;
    }
    else {
        /* user-space buffered I/O */
        res = 0;
        while (1) {
            if (nBufReq <= sa->nReadLen) {
                /* buffer holds enough data, so just use this */
                memmove(cpBuf, sa->cpReadBuf, nBufReq);
                memmove(sa->cpReadBuf, sa->cpReadBuf+nBufReq, sa->nReadLen-nBufReq);
                sa->nReadLen -= nBufReq;
                res += nBufReq;
            }
            else {
                if (sa->nReadLen > 0) {
                    /* fetch already existing buffer contents as a start */
                    memmove(cpBuf, sa->cpReadBuf, sa->nReadLen);
                    nBufReq -= sa->nReadLen;
                    cpBuf   += sa->nReadLen;
                    res     += sa->nReadLen;
                    sa->nReadLen = 0;
                }
                if (nBufReq >= sa->nReadSize) {
                    /* buffer is too small at all, so read directly */
                    n = sa_read_raw(sa, cpBuf, nBufReq);
                    if (n > 0)
                        res += n;
                    else if (n == 0)
                        rv = (res == 0 ? SA_ERR_EOF : SA_OK);
                    else if (n < 0 && errno == ETIMEDOUT)
                        rv = (res == 0 ? SA_ERR_TMT : SA_OK);
                    else if (n < 0)
                        rv = (res == 0 ? SA_ERR_SYS : SA_OK);
                }
                else {
                    /* fill buffer with new data */
                    n = sa_read_raw(sa, sa->cpReadBuf, sa->nReadSize);
                    if (n < 0 && errno == ETIMEDOUT)
                        /* timeout on this read, but perhaps ok as a whole */
                        rv = (res == 0 ? SA_ERR_TMT : SA_OK);
                    else if (n < 0)
                        /* error on this read, but perhaps ok as a whole */
                        rv = (res == 0 ? SA_ERR_SYS : SA_OK);
                    if (n == 0)
                        /* EOF on this read, but perhaps ok as a whole */
                        rv = (res == 0 ? SA_ERR_EOF : SA_OK);
                    else {
                        sa->nReadLen = n;
                        continue; /* REPEAT OPERATION! */
                    }
                }
            }
            break;
        }
    }

    /* pass number of actually read bytes to caller */ 
    if (nBufRes != NULL)
        *nBufRes = res;

    return rv;
}

/* read data from socket until [CR]LF (convinience function) */
sa_rc_t sa_readln(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
    char c;
    size_t n;
    size_t res;

    /* argument sanity check(s) */
    if (sa == NULL || cpBuf == NULL || nBufReq == 0)
        return SA_ERR_ARG;

    /* reading is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* we just perform a plain sa_read() per character, because if
       buffers are enabled, this is not as stupid as it looks at the first
       hand and if buffers are disabled, there is no better solution
       anyway. */
    res = 0;
    while (res < (nBufReq-1)) {
        sa_read(sa, &c, 1, &n);
        if (n <= 0)
            break;
        cpBuf[res++] = c;
        if (c == '\n')
            break;
    }
    cpBuf[res] = '\0';

    /* pass number of actually read characters to caller */
    if (nBufRes != NULL)
        *nBufRes = res;

    return SA_OK;
}

/* internal raw write operation */
static int sa_write_raw(sa_t *sa, const char *cpBuf, int nBufLen)
{
    int rv;
#if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO))
    fd_set fds;
#endif

    /* if timeout is enabled, perform explicit/smart blocking instead 
       of implicitly/hard blocking in the write(2) system call */
#if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO))
    if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
        FD_ZERO(&fds);
        FD_SET(sa->fdSocket, &fds);
        do {
            rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL, 
                              &sa->tvTimeout[SA_TIMEOUT_WRITE]);
        } while (rv == -1 && errno == EINTR);
        if (rv == 0) {
            errno = ETIMEDOUT;
            return -1;
        }
    }
#endif

    /* perform write operation on underlying socket */
    do {
        rv = SA_SC_CALL_3(sa, write, sa->fdSocket, cpBuf, nBufLen);
    } while (rv == -1 && errno == EINTR);

#if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)
    if (rv == -1 && errno == EWOULDBLOCK)
        errno = ETIMEDOUT;
#endif

    return rv;
}

/* write data to socket */
sa_rc_t sa_write(sa_t *sa, const char *cpBuf, size_t nBufReq, size_t *nBufRes)
{
    size_t n;
    size_t res;
    sa_rc_t rv;

    /* argument sanity check(s) */
    if (sa == NULL || cpBuf == NULL || nBufReq == 0)
        return SA_ERR_ARG;

    /* writing is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    rv = SA_OK;
    if (sa->nWriteSize == 0) {
        /* user-space unbuffered I/O */
        res = sa_write_raw(sa, cpBuf, nBufReq);
        if (res < 0 && errno == ETIMEDOUT)
            rv = SA_ERR_TMT;
        else if (res < 0)
            rv = SA_ERR_SYS;
    }
    else {
        /* user-space buffered I/O */
        if (nBufReq > (sa->nWriteSize - sa->nWriteLen)) {
            /* not enough space in buffer, so flush buffer first */
            sa_flush(sa);
        }
        res = 0;
        if (nBufReq >= sa->nWriteSize) {
            /* buffer too small at all, so write immediately */
            while (nBufReq > 0) {
                n = sa_write_raw(sa, cpBuf, nBufReq);
                if (n < 0 && errno == ETIMEDOUT)
                    rv = (res == 0 ? SA_ERR_TMT : SA_OK);
                else if (n < 0)
                    rv = (res == 0 ? SA_ERR_SYS : SA_OK);
                if (n <= 0)
                    break;
                nBufReq  -= n;
                cpBuf    += n;
                res      += n;
            }
        }
        else {
            /* (again) enough sprace in buffer, so store data */
            memmove(sa->cpWriteBuf+sa->nWriteLen, cpBuf, nBufReq);
            sa->nWriteLen += nBufReq;
            res = nBufReq;
        }
    }

    /* pass number of actually written bytes to caller */
    if (nBufRes != NULL)
        *nBufRes = res;

    return rv;
}

/* output callback function context for sa_writef() */
typedef struct {
    sa_t *sa;
    sa_rc_t rv;
} sa_writef_cb_t;

/* output callback function for sa_writef() */
static int sa_writef_cb(void *_ctx, const char *buffer, size_t bufsize)
{
    size_t n;
    sa_writef_cb_t *ctx = (sa_writef_cb_t *)_ctx;

    if ((ctx->rv = sa_write(ctx->sa, buffer, bufsize, &n)) != SA_OK)
        n = -1;
    return n;
}

/* write formatted string to socket (convinience function) */
sa_rc_t sa_writef(sa_t *sa, const char *cpFmt, ...)
{
    va_list ap;
    size_t n;
    sa_writef_cb_t ctx;

    /* argument sanity check(s) */
    if (sa == NULL || cpFmt == NULL)
        return SA_ERR_ARG;

    /* writing is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* format string into temporary buffer */
    va_start(ap, cpFmt);
    ctx.sa = sa;
    ctx.rv = SA_OK;
    n = sa_mvxprintf(sa_writef_cb, &ctx, cpFmt, ap);
    va_end(ap);

    return ctx.rv;
}

/* flush write/outgoing I/O buffer */
sa_rc_t sa_flush(sa_t *sa)
{
    size_t n;
    sa_rc_t rv;

    /* argument sanity check(s) */
    if (sa == NULL)
        return SA_ERR_ARG;

    /* flushing is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* try to flush buffer */
    rv = SA_OK;
    if (sa->nWriteSize > 0) {
        while (sa->nWriteLen > 0) {
            n = sa_write_raw(sa, sa->cpWriteBuf, sa->nWriteLen);
            if (n < 0 && errno == ETIMEDOUT)
                rv = SA_ERR_TMT;
            else if (n < 0)
                rv = SA_ERR_SYS;
            if (n <= 0)
                break;
            memmove(sa->cpWriteBuf, sa->cpWriteBuf+n, sa->nWriteLen-n);
            sa->nWriteLen -= n;
        }
        sa->nWriteLen = 0;
    }
    return rv;
}

/* shutdown a socket connection */
sa_rc_t sa_shutdown(sa_t *sa, char *flags)
{
    int how;

    /* argument sanity check(s) */
    if (sa == NULL || flags == NULL)
        return SA_ERR_ARG;

    /* shutdown is only possible for stream communication */ 
    if (sa->eType != SA_TYPE_STREAM)
        return SA_ERR_USE;

    /* at least a connection has to exist */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* determine flags for shutdown(2) */
    how = 0;
    if (strcmp(flags, "r") == 0)
        how = SHUT_RD;
    else if (strcmp(flags, "w") == 0)
        how = SHUT_WR;
    else if (strcmp(flags, "rw") == 0)
        how = SHUT_RDWR;
    else
        return SA_ERR_ARG;

    /* perform shutdown operation on underlying socket */
    if (shutdown(sa->fdSocket, how) == -1)
        return SA_ERR_SYS;

    return SA_OK;
}

/* receive data via socket */
sa_rc_t sa_recv(sa_t *sa, char *buf, size_t buflen, size_t *bufdone, sa_addr_t **raddr)
{
    sa_rc_t rv;
    union {
        struct sockaddr_in sa4;
#ifdef AF_INET6
        struct sockaddr_in6 sa6;
#endif
    } sa_buf;
    socklen_t sa_len;
    size_t n;
    fd_set fds;

    /* argument sanity check(s) */
    if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL)
        return SA_ERR_ARG;

    /* receiving is only possible for datagram communication */ 
    if (sa->eType != SA_TYPE_DATAGRAM)
        return SA_ERR_USE;

    /* at least a sa_bind() has to be performed */
    if (sa->fdSocket == -1)
        return SA_ERR_USE;

    /* if timeout is enabled, perform explicit/smart blocking instead 
       of implicitly/hard blocking in the recvfrom(2) system call */
    if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) {
        FD_ZERO(&fds);
        FD_SET(sa->fdSocket, &fds);
        do {
            n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL, 
                             &sa->tvTimeout[SA_TIMEOUT_READ]);
        } while (n == -1 && errno == EINTR);
        if (n == 0) 
            errno = ETIMEDOUT;
        if (n <= 0)
            return SA_ERR_SYS;
    }

    /* perform receive operation on underlying socket */
    sa_len = sizeof(sa_buf);
    if ((n = SA_SC_CALL_6(sa, recvfrom, sa->fdSocket, buf, buflen, 0, 
                          (struct sockaddr *)&sa_buf, &sa_len)) == -1)
        return SA_ERR_SYS;

    /* create result address object */
    if ((rv = sa_addr_create(raddr)) != SA_OK)
        return rv;
    if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_len)) != SA_OK) {
        sa_addr_destroy(*raddr);
        return rv;
    }

    /* pass actual number of received bytes to caller */
    if (bufdone != NULL)
        *bufdone = n;

    return SA_OK;
}

/* send data via socket */
sa_rc_t sa_send(sa_t *sa, const char *buf, size_t buflen, size_t *bufdone, sa_addr_t *raddr)
{
    size_t n;
    fd_set fds;
    sa_rc_t rv;

    /* argument sanity check(s) */
    if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL)
        return SA_ERR_ARG;

    /* sending is only possible for datagram communication */ 
    if (sa->eType != SA_TYPE_DATAGRAM)
        return SA_ERR_USE;

    /* lazy creation of underlying socket */
    if (sa->fdSocket == -1)
        if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK)
            return rv;

    /* if timeout is enabled, perform explicit/smart blocking instead 
       of implicitly/hard blocking in the sendto(2) system call */
    if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) {
        FD_ZERO(&fds);
        FD_SET(sa->fdSocket, &fds);
        do {
            n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL, 
                             &sa->tvTimeout[SA_TIMEOUT_WRITE]);
        } while (n == -1 && errno == EINTR);
        if (n == 0) 
            errno = ETIMEDOUT;
        if (n <= 0)
            return SA_ERR_SYS;
    }

    /* perform send operation on underlying socket */
    if ((n = SA_SC_CALL_6(sa, sendto, sa->fdSocket, buf, buflen, 0, raddr->saBuf, raddr->slBuf)) == -1)
        return SA_ERR_SYS;

    /* pass actual number of sent bytes to caller */
    if (bufdone != NULL)
        *bufdone = n;

    return SA_OK;
}

/* return error string */
char *sa_error(sa_rc_t rv)
{
    char *sz;

    /* translate result value into corresponding string */
    if      (rv == SA_OK)      sz = "Everything Ok";
    else if (rv == SA_ERR_ARG) sz = "Invalid Argument";
    else if (rv == SA_ERR_USE) sz = "Invalid Use Or Context";
    else if (rv == SA_ERR_MEM) sz = "Not Enough Memory";
    else if (rv == SA_ERR_MTC) sz = "Matching Failed";
    else if (rv == SA_ERR_EOF) sz = "End Of Communication";
    else if (rv == SA_ERR_TMT) sz = "Communication Timeout";
    else if (rv == SA_ERR_SYS) sz = "Operating System Error";
    else if (rv == SA_ERR_INT) sz = "Internal Error";
    else                       sz = "Invalid Result Code";
    return sz;
}

