/*
 * librd - Rapid Development C library
 *
 * Copyright (c) 2012, Magnus Edenhill
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
 */



#include "rd.h"
#include "rdaddr.h"
#include "rdrand.h"

#ifdef _WIN32
#include <ws2tcpip.h>
#endif

const char *rd_sockaddr2str(const void *addr, int flags) {
        const rd_sockaddr_inx_t *a = (const rd_sockaddr_inx_t *)addr;
        static RD_TLS char ret[32][256];
        static RD_TLS int reti = 0;
        char portstr[32];
        int of      = 0;
        int niflags = NI_NUMERICSERV;
        int r;

        reti = (reti + 1) % 32;

        switch (a->sinx_family) {
        case AF_INET:
        case AF_INET6:
                if (flags & RD_SOCKADDR2STR_F_FAMILY)
                        of += rd_snprintf(&ret[reti][of],
                                          sizeof(ret[reti]) - of, "ipv%i#",
                                          a->sinx_family == AF_INET ? 4 : 6);

                if ((flags & RD_SOCKADDR2STR_F_PORT) &&
                    a->sinx_family == AF_INET6)
                        ret[reti][of++] = '[';

                if (!(flags & RD_SOCKADDR2STR_F_RESOLVE))
                        niflags |= NI_NUMERICHOST;

        retry:
                if ((r = getnameinfo(
                         (const struct sockaddr *)a, RD_SOCKADDR_INX_LEN(a),

                         ret[reti] + of, sizeof(ret[reti]) - of,

                         (flags & RD_SOCKADDR2STR_F_PORT) ? portstr : NULL,

                         (flags & RD_SOCKADDR2STR_F_PORT) ? sizeof(portstr) : 0,

                         niflags))) {

                        if (r == EAI_AGAIN && !(niflags & NI_NUMERICHOST)) {
                                /* If unable to resolve name, retry without
                                 * name resolution. */
                                niflags |= NI_NUMERICHOST;
                                goto retry;
                        }
                        break;
                }


                if (flags & RD_SOCKADDR2STR_F_PORT) {
                        size_t len = strlen(ret[reti]);
                        rd_snprintf(
                            ret[reti] + len, sizeof(ret[reti]) - len, "%s:%s",
                            a->sinx_family == AF_INET6 ? "]" : "", portstr);
                }

                return ret[reti];
        }


        /* Error-case */
        rd_snprintf(ret[reti], sizeof(ret[reti]), "<unsupported:%s>",
                    rd_family2str(a->sinx_family));

        return ret[reti];
}


const char *rd_addrinfo_prepare(const char *nodesvc, char **node, char **svc) {
        static RD_TLS char snode[256];
        static RD_TLS char ssvc[64];
        const char *t;
        const char *svct = NULL;
        size_t nodelen   = 0;

        *snode = '\0';
        *ssvc  = '\0';

        if (*nodesvc == '[') {
                /* "[host]".. (enveloped node name) */
                if (!(t = strchr(nodesvc, ']')))
                        return "Missing close-']'";
                nodesvc++;
                nodelen = t - nodesvc;
                svct    = t + 1;

        } else if (*nodesvc == ':' && *(nodesvc + 1) != ':') {
                /* ":"..  (port only) */
                nodelen = 0;
                svct    = nodesvc;
        }

        if ((svct = strrchr(svct ? svct : nodesvc, ':')) &&
            (*(svct - 1) != ':') && *(++svct)) {
                /* Optional ":service" definition. */
                if (strlen(svct) >= sizeof(ssvc))
                        return "Service name too long";
                strcpy(ssvc, svct);
                if (!nodelen)
                        nodelen = svct - nodesvc - 1;

        } else if (!nodelen)
                nodelen = strlen(nodesvc);

        if (nodelen) {
                /* Truncate nodename if necessary. */
                nodelen = RD_MIN(nodelen, sizeof(snode) - 1);
                memcpy(snode, nodesvc, nodelen);
                snode[nodelen] = '\0';
        }

        *node = snode;
        *svc  = ssvc;

        return NULL;
}



rd_sockaddr_list_t *rd_getaddrinfo(const char *nodesvc,
                                   const char *defsvc,
                                   int flags,
                                   int family,
                                   int socktype,
                                   int protocol,
                                   const char **errstr) {
        struct addrinfo hints;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family   = family;
        hints.ai_socktype = socktype;
        hints.ai_protocol = protocol;
        hints.ai_flags    = flags;

        struct addrinfo *ais, *ai;
        char *node, *svc;
        int r;
        int cnt = 0;
        rd_sockaddr_list_t *rsal;

        if ((*errstr = rd_addrinfo_prepare(nodesvc, &node, &svc))) {
                errno = EINVAL;
                return NULL;
        }

        if (*svc)
                defsvc = svc;

        if ((r = getaddrinfo(node, defsvc, &hints, &ais))) {
#ifdef EAI_SYSTEM
                if (r == EAI_SYSTEM)
#else
                if (0)
#endif
                        *errstr = rd_strerror(errno);
                else {
#ifdef _WIN32
                        *errstr = gai_strerrorA(r);
#else
                        *errstr = gai_strerror(r);
#endif
                        errno = EFAULT;
                }
                return NULL;
        }

        /* Count number of addresses */
        for (ai = ais; ai != NULL; ai = ai->ai_next)
                cnt++;

        if (cnt == 0) {
                /* unlikely? */
                freeaddrinfo(ais);
                errno   = ENOENT;
                *errstr = "No addresses";
                return NULL;
        }


        rsal = rd_calloc(1, sizeof(*rsal) + (sizeof(*rsal->rsal_addr) * cnt));

        for (ai = ais; ai != NULL; ai = ai->ai_next)
                memcpy(&rsal->rsal_addr[rsal->rsal_cnt++], ai->ai_addr,
                       ai->ai_addrlen);

        freeaddrinfo(ais);

        /* Shuffle address list for proper round-robin */
        if (!(flags & RD_AI_NOSHUFFLE))
                rd_array_shuffle(rsal->rsal_addr, rsal->rsal_cnt,
                                 sizeof(*rsal->rsal_addr));

        return rsal;
}



void rd_sockaddr_list_destroy(rd_sockaddr_list_t *rsal) {
        rd_free(rsal);
}
