
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */

#include <nxt_main.h>


#if (NXT_INET6)
static u_char *nxt_inet6_ntop(u_char *addr, u_char *buf, u_char *end);
#endif

static nxt_sockaddr_t *nxt_sockaddr_unix_parse(nxt_mp_t *mp, nxt_str_t *addr);
static nxt_sockaddr_t *nxt_sockaddr_inet6_parse(nxt_mp_t *mp, nxt_str_t *addr);
static nxt_sockaddr_t *nxt_sockaddr_inet_parse(nxt_mp_t *mp, nxt_str_t *addr);

static nxt_int_t nxt_job_sockaddr_unix_parse(nxt_job_sockaddr_parse_t *jbs);
static nxt_int_t nxt_job_sockaddr_inet6_parse(nxt_job_sockaddr_parse_t *jbs);
static nxt_int_t nxt_job_sockaddr_inet_parse(nxt_job_sockaddr_parse_t *jbs);


nxt_sockaddr_t *
nxt_sockaddr_cache_alloc(nxt_event_engine_t *engine, nxt_listen_socket_t *ls)
{
    size_t          size;
    uint8_t         hint;
    nxt_sockaddr_t  *sa;

    hint = NXT_EVENT_ENGINE_NO_MEM_HINT;
    size = offsetof(nxt_sockaddr_t, u) + ls->socklen + ls->address_length;

    sa = nxt_event_engine_mem_alloc(engine, &hint, size);

    if (nxt_fast_path(sa != NULL)) {
        /* Zero only beginning of structure up to sockaddr_un.sun_path[1]. */
        nxt_memzero(sa, offsetof(nxt_sockaddr_t, u.sockaddr.sa_data[1]));

        sa->cache_hint = hint;
        sa->socklen = ls->socklen;
        sa->length = ls->address_length;

        sa->type = ls->sockaddr->type;
        /*
         * Set address family for unspecified Unix domain socket,
         * because these sockaddr's are not updated by old BSD systems,
         * see comment in nxt_conn_io_accept().
         */
        sa->u.sockaddr.sa_family = ls->sockaddr->u.sockaddr.sa_family;
    }

    return sa;
}


void
nxt_sockaddr_cache_free(nxt_event_engine_t *engine, nxt_conn_t *c)
{
    nxt_sockaddr_t  *sa;

    sa = c->remote;

    nxt_event_engine_mem_free(engine, sa->cache_hint, sa, 0);
}


nxt_sockaddr_t *
nxt_sockaddr_alloc(nxt_mp_t *mp, socklen_t socklen, size_t address_length)
{
    size_t          size;
    nxt_sockaddr_t  *sa;

    size = offsetof(nxt_sockaddr_t, u) + socklen + address_length;

    /*
     * The current struct sockaddr's define 32-bit fields at maximum
     * and may define 64-bit AF_INET6 fields in the future.  Alignment
     * of memory allocated by nxt_mp_zalloc() is enough for these fields.
     * If 128-bit alignment will be required then nxt_mem_malloc() and
     * nxt_memzero() should be used instead.
     */

    sa = nxt_mp_zalloc(mp, size);

    if (nxt_fast_path(sa != NULL)) {
        sa->socklen = socklen;
        sa->length = address_length;
    }

    return sa;
}


nxt_sockaddr_t *
nxt_sockaddr_create(nxt_mp_t *mp, struct sockaddr *sockaddr, socklen_t length,
    size_t address_length)
{
    size_t          size, copy;
    nxt_sockaddr_t  *sa;

    size = length;
    copy = length;

#if (NXT_HAVE_UNIX_DOMAIN)

    /*
     * Unspecified Unix domain sockaddr_un form and length are very
     * platform depended (see comment in nxt_socket.h).  Here they are
     * normalized to the sockaddr_un with single zero byte sun_path[].
     */

    if (size <= offsetof(struct sockaddr_un, sun_path)) {
        /*
         * Small socket length means a short unspecified Unix domain
         * socket address:
         *
         *   getsockname() and getpeername() on OpenBSD prior to 5.3
         *   return zero length and does not update a passed sockaddr
         *   buffer at all.
         *
         *   Linux returns length equal to 2, i.e. sockaddr_un without
         *   sun_path[], unix(7):
         *
         *     unnamed: A stream socket that has not been bound
         *     to a pathname using bind(2) has no name.  Likewise,
         *     the two sockets created by socketpair(2) are unnamed.
         *     When the address of an unnamed socket is returned by
         *     getsockname(2), getpeername(2), and accept(2), its
         *     length is sizeof(sa_family_t), and sun_path should
         *     not be inspected.
         */
        size = offsetof(struct sockaddr_un, sun_path) + 1;

#if !(NXT_LINUX)

    } else if (sockaddr->sa_family == AF_UNIX && sockaddr->sa_data[0] == '\0') {
        /*
         * Omit nonsignificant zeros of the unspecified Unix domain socket
         * address.  This test is disabled for Linux since Linux abstract
         * socket address also starts with zero.  However Linux unspecified
         * Unix domain socket address is short and is handled above.
         */
        size = offsetof(struct sockaddr_un, sun_path) + 1;
        copy = size;

#endif
    }

#endif  /* NXT_HAVE_UNIX_DOMAIN */

    sa = nxt_sockaddr_alloc(mp, size, address_length);

    if (nxt_fast_path(sa != NULL)) {
        nxt_memcpy(&sa->u.sockaddr, sockaddr, copy);

#if (NXT_HAVE_UNIX_DOMAIN && NXT_OPENBSD)

        if (length == 0) {
            sa->u.sockaddr.sa_family = AF_UNIX;
        }

#endif
    }

    return sa;
}


nxt_sockaddr_t *
nxt_sockaddr_copy(nxt_mp_t *mp, nxt_sockaddr_t *src)
{
    size_t          length;
    nxt_sockaddr_t  *dst;

    length = offsetof(nxt_sockaddr_t, u) + src->socklen;

    dst = nxt_mp_alloc(mp, length);

    if (nxt_fast_path(dst != NULL)) {
        nxt_memcpy(dst, src, length);
    }

    return dst;
}


nxt_sockaddr_t *
nxt_getsockname(nxt_task_t *task, nxt_mp_t *mp, nxt_socket_t s)
{
    int                 ret;
    size_t              length;
    socklen_t           socklen;
    nxt_sockaddr_buf_t  sockaddr;

    socklen = NXT_SOCKADDR_LEN;

    ret = getsockname(s, &sockaddr.buf, &socklen);

    if (nxt_fast_path(ret == 0)) {

        switch (sockaddr.buf.sa_family) {
#if (NXT_INET6)
        case AF_INET6:
            length = NXT_INET6_ADDR_STR_LEN;
            break;
#endif

#if (NXT_HAVE_UNIX_DOMAIN)
        case AF_UNIX:
            length = nxt_length("unix:") + socklen;
#endif
            break;

        case AF_INET:
            length = NXT_INET_ADDR_STR_LEN;
            break;

        default:
            length = 0;
            break;
        }

        return nxt_sockaddr_create(mp, &sockaddr.buf, socklen, length);
    }

    nxt_log(task, NXT_LOG_ERR, "getsockname(%d) failed %E", s, nxt_errno);

    return NULL;
}


void
nxt_sockaddr_text(nxt_sockaddr_t *sa)
{
    size_t    offset;
    u_char    *p, *start, *end, *octet;
    uint32_t  port;

    offset = offsetof(nxt_sockaddr_t, u) + sa->socklen;
    sa->start = offset;
    sa->port_start = offset;

    start = nxt_pointer_to(sa, offset);
    end = start + sa->length;

    switch (sa->u.sockaddr.sa_family) {

    case AF_INET:
        sa->address_start = offset;

        octet = (u_char *) &sa->u.sockaddr_in.sin_addr;

        p = nxt_sprintf(start, end, "%ud.%ud.%ud.%ud",
                        octet[0], octet[1], octet[2], octet[3]);

        sa->address_length = p - start;
        sa->port_start += sa->address_length + 1;

        port = sa->u.sockaddr_in.sin_port;

        break;

#if (NXT_INET6)

    case AF_INET6:
        sa->address_start = offset + 1;

        p = start;
        *p++ = '[';

        p = nxt_inet6_ntop(sa->u.sockaddr_in6.sin6_addr.s6_addr, p, end);

        sa->address_length = p - (start + 1);
        sa->port_start += sa->address_length + 3;

        *p++ = ']';

        port = sa->u.sockaddr_in6.sin6_port;

        break;

#endif

#if (NXT_HAVE_UNIX_DOMAIN)

    case AF_UNIX:
        sa->address_start = offset;

        p = (u_char *) sa->u.sockaddr_un.sun_path;

#if (NXT_LINUX)

        if (p[0] == '\0') {
            size_t  length;

            /* Linux abstract socket address has no trailing zero. */
            length = sa->socklen - offsetof(struct sockaddr_un, sun_path);

            p = nxt_sprintf(start, end, "unix:@%*s", length - 1, p + 1);

        } else {
            p = nxt_sprintf(start, end, "unix:%s", p);
        }

#else  /* !(NXT_LINUX) */

        p = nxt_sprintf(start, end, "unix:%s", p);

#endif

        sa->address_length = p - start;
        sa->port_start += sa->address_length;
        sa->length = p - start;

        return;

#endif  /* NXT_HAVE_UNIX_DOMAIN */

    default:
        return;
    }

    p = nxt_sprintf(p, end, ":%d", ntohs(port));

    sa->length = p - start;
}


uint32_t
nxt_sockaddr_port_number(nxt_sockaddr_t *sa)
{
    uint32_t  port;

    switch (sa->u.sockaddr.sa_family) {

#if (NXT_INET6)

    case AF_INET6:
        port = sa->u.sockaddr_in6.sin6_port;
        break;

#endif

#if (NXT_HAVE_UNIX_DOMAIN)

    case AF_UNIX:
        return 0;

#endif

    default:
        port = sa->u.sockaddr_in.sin_port;
        break;
    }

    return ntohs((uint16_t) port);
}


nxt_bool_t
nxt_sockaddr_cmp(nxt_sockaddr_t *sa1, nxt_sockaddr_t *sa2)
{
    if (sa1->socklen != sa2->socklen) {
        return 0;
    }

    if (sa1->type != sa2->type) {
        return 0;
    }

    if (sa1->u.sockaddr.sa_family != sa2->u.sockaddr.sa_family) {
        return 0;
    }

    /*
     * sockaddr struct's cannot be compared in whole since kernel
     * may fill some fields in inherited sockaddr struct's.
     */

    switch (sa1->u.sockaddr.sa_family) {

#if (NXT_INET6)

    case AF_INET6:
        if (sa1->u.sockaddr_in6.sin6_port != sa2->u.sockaddr_in6.sin6_port) {
            return 0;
        }

        if (nxt_memcmp(&sa1->u.sockaddr_in6.sin6_addr,
                       &sa2->u.sockaddr_in6.sin6_addr, 16)
            != 0)
        {
            return 0;
        }

        return 1;

#endif

#if (NXT_HAVE_UNIX_DOMAIN)

    case AF_UNIX:
        {
            size_t  length;

            length = sa1->socklen - offsetof(struct sockaddr_un, sun_path);

            if (nxt_memcmp(&sa1->u.sockaddr_un.sun_path,
                           &sa2->u.sockaddr_un.sun_path, length)
                != 0)
            {
                return 0;
            }

            return 1;
        }

#endif

    default: /* AF_INET */
        if (sa1->u.sockaddr_in.sin_port != sa2->u.sockaddr_in.sin_port) {
            return 0;
        }

        if (sa1->u.sockaddr_in.sin_addr.s_addr
            != sa2->u.sockaddr_in.sin_addr.s_addr)
        {
            return 0;
        }

        return 1;
    }
}


size_t
nxt_sockaddr_ntop(nxt_sockaddr_t *sa, u_char *buf, u_char *end, nxt_bool_t port)
{
    u_char  *p;

    switch (sa->u.sockaddr.sa_family) {

    case AF_INET:
        p = (u_char *) &sa->u.sockaddr_in.sin_addr;

        if (port) {
            p = nxt_sprintf(buf, end, "%ud.%ud.%ud.%ud:%d",
                            p[0], p[1], p[2], p[3],
                            ntohs(sa->u.sockaddr_in.sin_port));
        } else {
            p = nxt_sprintf(buf, end, "%ud.%ud.%ud.%ud",
                            p[0], p[1], p[2], p[3]);
        }

        return p - buf;

#if (NXT_INET6)

    case AF_INET6:
        p = buf;

        if (port) {
            *p++ = '[';
        }

        p = nxt_inet6_ntop(sa->u.sockaddr_in6.sin6_addr.s6_addr, p, end);

        if (port) {
            p = nxt_sprintf(p, end, "]:%d",
                            ntohs(sa->u.sockaddr_in6.sin6_port));
        }

        return p - buf;
#endif

#if (NXT_HAVE_UNIX_DOMAIN)

    case AF_UNIX:

#if (NXT_LINUX)

        p = (u_char *) sa->u.sockaddr_un.sun_path;

        if (p[0] == '\0') {
            size_t  length;

            /* Linux abstract socket address has no trailing zero. */

            length = sa->socklen - offsetof(struct sockaddr_un, sun_path) - 1;
            p = nxt_sprintf(buf, end, "unix:\\0%*s", length, p + 1);

        } else {
            p = nxt_sprintf(buf, end, "unix:%s", p);
        }

#else  /* !(NXT_LINUX) */

        p = nxt_sprintf(buf, end, "unix:%s", sa->u.sockaddr_un.sun_path);

#endif

        return p - buf;

#endif  /* NXT_HAVE_UNIX_DOMAIN */

    default:
        return 0;
    }
}


#if (NXT_INET6)

static u_char *
nxt_inet6_ntop(u_char *addr, u_char *buf, u_char *end)
{
    u_char       *p;
    size_t       zero_groups, last_zero_groups, ipv6_bytes;
    nxt_uint_t   i, zero_start, last_zero_start;

    const size_t  max_inet6_length =
                        nxt_length("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");

    if (buf + max_inet6_length > end) {
        return buf;
    }

    zero_start = 8;
    zero_groups = 0;
    last_zero_start = 8;
    last_zero_groups = 0;

    for (i = 0; i < 16; i += 2) {

        if (addr[i] == 0 && addr[i + 1] == 0) {

            if (last_zero_groups == 0) {
                last_zero_start = i;
            }

            last_zero_groups++;

        } else {
            if (zero_groups < last_zero_groups) {
                zero_groups = last_zero_groups;
                zero_start = last_zero_start;
            }

            last_zero_groups = 0;
        }
    }

    if (zero_groups < last_zero_groups) {
        zero_groups = last_zero_groups;
        zero_start = last_zero_start;
    }

    ipv6_bytes = 16;
    p = buf;

    if (zero_start == 0) {

               /* IPv4-mapped address */
        if ((zero_groups == 5 && addr[10] == 0xFF && addr[11] == 0xFF)
               /* IPv4-compatible address */
            || (zero_groups == 6)
               /* not IPv6 loopback address */
            || (zero_groups == 7 && addr[14] != 0 && addr[15] != 1))
        {
            ipv6_bytes = 12;
        }

        *p++ = ':';
    }

    for (i = 0; i < ipv6_bytes; i += 2) {

        if (i == zero_start) {
            /* Output maximum number of consecutive zero groups as "::". */
            i += (zero_groups - 1) * 2;
            *p++ = ':';
            continue;
        }

        p = nxt_sprintf(p, end, "%uxd", (addr[i] << 8) + addr[i + 1]);

        if (i < 14) {
            *p++ = ':';
        }
    }

    if (ipv6_bytes == 12) {
        p = nxt_sprintf(p, end, "%ud.%ud.%ud.%ud",
                        addr[12], addr[13], addr[14], addr[15]);
    }

    return p;
}

#endif


nxt_sockaddr_t *
nxt_sockaddr_parse(nxt_mp_t *mp, nxt_str_t *addr)
{
    nxt_sockaddr_t  *sa;

    if (addr->length > 6 && nxt_memcmp(addr->start, "unix:", 5) == 0) {
        sa = nxt_sockaddr_unix_parse(mp, addr);

    } else if (addr->length != 0 && addr->start[0] == '[') {
        sa = nxt_sockaddr_inet6_parse(mp, addr);

    } else {
        sa = nxt_sockaddr_inet_parse(mp, addr);
    }

    if (nxt_fast_path(sa != NULL)) {
        nxt_sockaddr_text(sa);
    }

    return sa;
}


static nxt_sockaddr_t *
nxt_sockaddr_unix_parse(nxt_mp_t *mp, nxt_str_t *addr)
{
#if (NXT_HAVE_UNIX_DOMAIN)
    size_t          length, socklen;
    u_char          *path;
    nxt_sockaddr_t  *sa;

    /*
     * Actual sockaddr_un length can be lesser or even larger than defined
     * struct sockaddr_un length (see comment in nxt_socket.h).  So
     * limit maximum Unix domain socket address length by defined sun_path[]
     * length because some OSes accept addresses twice larger than defined
     * struct sockaddr_un.  Also reserve space for a trailing zero to avoid
     * ambiguity, since many OSes accept Unix domain socket addresses
     * without a trailing zero.
     */
    const size_t max_len = sizeof(struct sockaddr_un)
                           - offsetof(struct sockaddr_un, sun_path) - 1;

    /* Cutting "unix:". */
    length = addr->length - 5;
    path = addr->start + 5;

    if (length > max_len) {
        nxt_thread_log_error(NXT_LOG_ERR,
                             "unix domain socket \"%V\" name is too long",
                             addr);
        return NULL;
    }

    socklen = offsetof(struct sockaddr_un, sun_path) + length + 1;

#if (NXT_LINUX)

    /*
     * Linux unix(7):
     *
     *   abstract: an abstract socket address is distinguished by the fact
     *   that sun_path[0] is a null byte ('\0').  The socket's address in
     *   this namespace is given by the additional bytes in sun_path that
     *   are covered by the specified length of the address structure.
     *   (Null bytes in the name have no special significance.)
     */
    if (path[0] == '@') {
        path[0] = '\0';
        socklen--;
    }

#endif

    sa = nxt_sockaddr_alloc(mp, socklen, addr->length);

    if (nxt_fast_path(sa != NULL)) {
        sa->u.sockaddr_un.sun_family = AF_UNIX;
        nxt_memcpy(sa->u.sockaddr_un.sun_path, path, length);
    }

    return sa;

#else  /* !(NXT_HAVE_UNIX_DOMAIN) */

    nxt_thread_log_error(NXT_LOG_ERR,
                         "unix domain socket \"%V\" is not supported", addr);

    return NULL;

#endif
}


static nxt_sockaddr_t *
nxt_sockaddr_inet6_parse(nxt_mp_t *mp, nxt_str_t *addr)
{
#if (NXT_INET6)
    u_char          *p, *start, *end;
    size_t          length;
    nxt_int_t       ret, port;
    nxt_sockaddr_t  *sa;

    length = addr->length - 1;
    start = addr->start + 1;

    end = nxt_memchr(start, ']', length);

    if (end != NULL) {
        sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in6),
                                NXT_INET6_ADDR_STR_LEN);
        if (nxt_slow_path(sa == NULL)) {
            return NULL;
        }

        ret = nxt_inet6_addr(&sa->u.sockaddr_in6.sin6_addr, start, end - start);

        if (nxt_fast_path(ret == NXT_OK)) {
            p = end + 1;
            length = (start + length) - p;

            if (length > 2 && *p == ':') {
                port = nxt_int_parse(p + 1, length - 1);

                if (port > 0 && port < 65536) {
                    sa->u.sockaddr_in6.sin6_port = htons((in_port_t) port);
                    sa->u.sockaddr_in6.sin6_family = AF_INET6;

                    return sa;
                }
            }

            nxt_thread_log_error(NXT_LOG_ERR, "invalid port in \"%V\"", addr);

            return NULL;
        }
    }

    nxt_thread_log_error(NXT_LOG_ERR, "invalid IPv6 address in \"%V\"", addr);

    return NULL;

#else  /* !(NXT_INET6) */

    nxt_thread_log_error(NXT_LOG_ERR, "IPv6 socket \"%V\" is not supported",
                         addr);
    return NULL;

#endif
}


static nxt_sockaddr_t *
nxt_sockaddr_inet_parse(nxt_mp_t *mp, nxt_str_t *addr)
{
    u_char          *p;
    size_t          length;
    nxt_int_t       port;
    in_addr_t       inaddr;
    nxt_sockaddr_t  *sa;

    p = nxt_memchr(addr->start, ':', addr->length);

    if (nxt_fast_path(p != NULL)) {
        inaddr = INADDR_ANY;
        length = p - addr->start;

        if (length != 1 || addr->start[0] != '*') {
            inaddr = nxt_inet_addr(addr->start, length);

            if (nxt_slow_path(inaddr == INADDR_NONE)) {
                nxt_thread_log_error(NXT_LOG_ERR, "invalid address \"%V\"",
                                     addr);
                return NULL;
            }
        }

        p++;
        length = (addr->start + addr->length) - p;
        port = nxt_int_parse(p, length);

        if (port > 0 && port < 65536) {
            sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in),
                                    NXT_INET_ADDR_STR_LEN);

            if (nxt_fast_path(sa != NULL)) {
                sa->u.sockaddr_in.sin_family = AF_INET;
                sa->u.sockaddr_in.sin_port = htons((in_port_t) port);
                sa->u.sockaddr_in.sin_addr.s_addr = inaddr;
            }

            return sa;
        }
    }

    nxt_thread_log_error(NXT_LOG_ERR, "invalid port in \"%V\"", addr);

    return NULL;
}


void
nxt_job_sockaddr_parse(nxt_job_sockaddr_parse_t *jbs)
{
    u_char              *p;
    size_t              length;
    nxt_int_t           ret;
    nxt_work_handler_t  handler;

    nxt_job_set_name(&jbs->resolve.job, "job sockaddr parse");

    length = jbs->addr.length;
    p = jbs->addr.start;

    if (length > 6 && nxt_memcmp(p, "unix:", 5) == 0) {
        ret = nxt_job_sockaddr_unix_parse(jbs);

    } else if (length != 0 && *p == '[') {
        ret = nxt_job_sockaddr_inet6_parse(jbs);

    } else {
        ret = nxt_job_sockaddr_inet_parse(jbs);
    }

    switch (ret) {

    case NXT_OK:
        handler = jbs->resolve.ready_handler;
        break;

    case NXT_ERROR:
        handler = jbs->resolve.error_handler;
        break;

    default: /* NXT_AGAIN */
        return;
    }

    nxt_job_return(jbs->resolve.job.task, &jbs->resolve.job, handler);
}


static nxt_int_t
nxt_job_sockaddr_unix_parse(nxt_job_sockaddr_parse_t *jbs)
{
#if (NXT_HAVE_UNIX_DOMAIN)
    size_t          length, socklen;
    u_char          *path;
    nxt_mp_t        *mp;
    nxt_sockaddr_t  *sa;

    /*
     * Actual sockaddr_un length can be lesser or even larger than defined
     * struct sockaddr_un length (see comment in nxt_socket.h).  So
     * limit maximum Unix domain socket address length by defined sun_path[]
     * length because some OSes accept addresses twice larger than defined
     * struct sockaddr_un.  Also reserve space for a trailing zero to avoid
     * ambiguity, since many OSes accept Unix domain socket addresses
     * without a trailing zero.
     */
    const size_t max_len = sizeof(struct sockaddr_un)
                           - offsetof(struct sockaddr_un, sun_path) - 1;

    /* cutting "unix:" */
    length = jbs->addr.length - 5;
    path = jbs->addr.start + 5;

    if (length > max_len) {
        nxt_thread_log_error(jbs->resolve.log_level,
                             "unix domain socket \"%V\" name is too long",
                             &jbs->addr);
        return NXT_ERROR;
    }

    socklen = offsetof(struct sockaddr_un, sun_path) + length + 1;

#if (NXT_LINUX)

    /*
     * Linux unix(7):
     *
     *   abstract: an abstract socket address is distinguished by the fact
     *   that sun_path[0] is a null byte ('\0').  The socket's address in
     *   this namespace is given by the additional bytes in sun_path that
     *   are covered by the specified length of the address structure.
     *   (Null bytes in the name have no special significance.)
     */
    if (path[0] == '\0') {
        socklen--;
    }

#endif

    mp = jbs->resolve.job.mem_pool;

    jbs->resolve.sockaddrs = nxt_mp_alloc(mp, sizeof(void *));

    if (nxt_fast_path(jbs->resolve.sockaddrs != NULL)) {
        sa = nxt_sockaddr_alloc(mp, socklen, jbs->addr.length);

        if (nxt_fast_path(sa != NULL)) {
            jbs->resolve.count = 1;
            jbs->resolve.sockaddrs[0] = sa;

            sa->u.sockaddr_un.sun_family = AF_UNIX;
            nxt_memcpy(sa->u.sockaddr_un.sun_path, path, length);

            return NXT_OK;
        }
    }

    return NXT_ERROR;

#else  /* !(NXT_HAVE_UNIX_DOMAIN) */

    nxt_thread_log_error(jbs->resolve.log_level,
                         "unix domain socket \"%V\" is not supported",
                         &jbs->addr);
    return NXT_ERROR;

#endif
}


static nxt_int_t
nxt_job_sockaddr_inet6_parse(nxt_job_sockaddr_parse_t *jbs)
{
#if (NXT_INET6)
    u_char           *p, *addr, *addr_end;
    size_t           length;
    nxt_mp_t         *mp;
    nxt_int_t        port;
    nxt_sockaddr_t   *sa;
    struct in6_addr  *in6_addr;

    length = jbs->addr.length - 1;
    addr = jbs->addr.start + 1;

    addr_end = nxt_memchr(addr, ']', length);

    if (addr_end == NULL) {
        goto invalid_address;
    }

    mp = jbs->resolve.job.mem_pool;

    jbs->resolve.sockaddrs = nxt_mp_alloc(mp, sizeof(void *));

    if (nxt_slow_path(jbs->resolve.sockaddrs == NULL)) {
        return NXT_ERROR;
    }

    sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in6),
                            NXT_INET6_ADDR_STR_LEN);

    if (nxt_slow_path(sa == NULL)) {
        return NXT_ERROR;
    }

    jbs->resolve.count = 1;
    jbs->resolve.sockaddrs[0] = sa;

    in6_addr = &sa->u.sockaddr_in6.sin6_addr;

    if (nxt_inet6_addr(in6_addr, addr, addr_end - addr) != NXT_OK) {
        goto invalid_address;
    }

    p = addr_end + 1;
    length = (addr + length) - p;

    if (length == 0) {
        jbs->no_port = 1;
        port = jbs->resolve.port;
        goto found;
    }

    if (*p == ':') {
        port = nxt_int_parse(p + 1, length - 1);

        if (port >= 1 && port <= 65535) {
            port = htons((in_port_t) port);
            goto found;
        }
    }

    nxt_thread_log_error(jbs->resolve.log_level,
                         "invalid port in \"%V\"", &jbs->addr);

    return NXT_ERROR;

found:

    sa->u.sockaddr_in6.sin6_family = AF_INET6;
    sa->u.sockaddr_in6.sin6_port = (in_port_t) port;

    if (IN6_IS_ADDR_UNSPECIFIED(in6_addr)) {
        jbs->wildcard = 1;
    }

    return NXT_OK;

invalid_address:

    nxt_thread_log_error(jbs->resolve.log_level,
                         "invalid IPv6 address in \"%V\"", &jbs->addr);
    return NXT_ERROR;

#else

    nxt_thread_log_error(jbs->resolve.log_level,
                         "IPv6 socket \"%V\" is not supported", &jbs->addr);
    return NXT_ERROR;

#endif
}


static nxt_int_t
nxt_job_sockaddr_inet_parse(nxt_job_sockaddr_parse_t *jbs)
{
    u_char          *p, *host;
    size_t          length;
    nxt_mp_t        *mp;
    nxt_int_t       port;
    in_addr_t       addr;
    nxt_sockaddr_t  *sa;

    addr = INADDR_ANY;

    length = jbs->addr.length;
    host = jbs->addr.start;

    p = nxt_memchr(host, ':', length);

    if (p == NULL) {

        /* single value port, address, or host name */

        port = nxt_int_parse(host, length);

        if (port > 0) {
            if (port < 1 || port > 65535) {
                goto invalid_port;
            }

            /* "*:XX" */
            port = htons((in_port_t) port);
            jbs->resolve.port = (in_port_t) port;

        } else {
            jbs->no_port = 1;

            addr = nxt_inet_addr(host, length);

            if (addr == INADDR_NONE) {
                jbs->resolve.name.length = length;
                jbs->resolve.name.start = host;

                nxt_job_resolve(&jbs->resolve);
                return NXT_AGAIN;
            }

            /* "x.x.x.x" */
            port = jbs->resolve.port;
        }

    } else {

        /* x.x.x.x:XX or host:XX */

        p++;
        length = (host + length) - p;
        port = nxt_int_parse(p, length);

        if (port < 1 || port > 65535) {
            goto invalid_port;
        }

        port = htons((in_port_t) port);

        length = (p - 1) - host;

        if (length != 1 || host[0] != '*') {
            addr = nxt_inet_addr(host, length);

            if (addr == INADDR_NONE) {
                jbs->resolve.name.length = length;
                jbs->resolve.name.start = host;
                jbs->resolve.port = (in_port_t) port;

                nxt_job_resolve(&jbs->resolve);
                return NXT_AGAIN;
            }

            /* "x.x.x.x:XX" */
        }
    }

    mp = jbs->resolve.job.mem_pool;

    jbs->resolve.sockaddrs = nxt_mp_alloc(mp, sizeof(void *));
    if (nxt_slow_path(jbs->resolve.sockaddrs == NULL)) {
        return NXT_ERROR;
    }

    sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in),
                            NXT_INET_ADDR_STR_LEN);

    if (nxt_fast_path(sa != NULL)) {
        jbs->resolve.count = 1;
        jbs->resolve.sockaddrs[0] = sa;

        jbs->wildcard = (addr == INADDR_ANY);

        sa->u.sockaddr_in.sin_family = AF_INET;
        sa->u.sockaddr_in.sin_port = (in_port_t) port;
        sa->u.sockaddr_in.sin_addr.s_addr = addr;

        return NXT_OK;
    }

    return NXT_ERROR;

invalid_port:

    nxt_thread_log_error(jbs->resolve.log_level,
                         "invalid port in \"%V\"", &jbs->addr);

    return NXT_ERROR;
}


in_addr_t
nxt_inet_addr(u_char *buf, size_t length)
{
    u_char      c, *end;
    in_addr_t   addr;
    nxt_uint_t  digit, octet, dots;

    if (nxt_slow_path(*(buf + length - 1) == '.')) {
        return INADDR_NONE;
    }

    addr = 0;
    octet = 0;
    dots = 0;

    end = buf + length;

    while (buf < end) {

        c = *buf++;

        digit = c - '0';
        /* values below '0' become large unsigned integers */

        if (digit < 10) {
            octet = octet * 10 + digit;
            continue;
        }

        if (c == '.' && octet < 256) {
            addr = (addr << 8) + octet;
            octet = 0;
            dots++;
            continue;
        }

        return INADDR_NONE;
    }

    if (dots == 3 && octet < 256) {
        addr = (addr << 8) + octet;
        return htonl(addr);
    }

    return INADDR_NONE;
}


#if (NXT_INET6)

nxt_int_t
nxt_inet6_addr(struct in6_addr *in6_addr, u_char *buf, size_t length)
{
    u_char      c, *addr, *zero_start, *ipv4, *dst, *src, *end;
    nxt_uint_t  digit, group, nibbles, groups_left;

    if (length == 0) {
        return NXT_ERROR;
    }

    end = buf + length;

    if (buf[0] == ':') {
        buf++;
    }

    addr = in6_addr->s6_addr;
    zero_start = NULL;
    groups_left = 8;
    nibbles = 0;
    group = 0;
    ipv4 = NULL;

    while (buf < end) {
        c = *buf++;

        if (c == ':') {
            if (nibbles != 0) {
                ipv4 = buf;

                *addr++ = (u_char) (group >> 8);
                *addr++ = (u_char) (group & 0xFF);
                groups_left--;

                if (groups_left != 0) {
                    nibbles = 0;
                    group = 0;
                    continue;
                }

            } else {
                if (zero_start == NULL) {
                    ipv4 = buf;
                    zero_start = addr;
                    continue;
                }
            }

            return NXT_ERROR;
        }

        if (c == '.' && nibbles != 0) {

            if (groups_left < 2 || ipv4 == NULL) {
                return NXT_ERROR;
            }

            group = nxt_inet_addr(ipv4, end - ipv4);
            if (group == INADDR_NONE) {
                return NXT_ERROR;
            }

            group = ntohl(group);

            *addr++ = (u_char) ((group >> 24) & 0xFF);
            *addr++ = (u_char) ((group >> 16) & 0xFF);
            groups_left--;

            /* the low 16-bit are copied below */
            break;
        }

        nibbles++;

        if (nibbles > 4) {
            return NXT_ERROR;
        }

        group <<= 4;

        digit = c - '0';
        /* values below '0' become large unsigned integers */

        if (digit < 10) {
            group += digit;
            continue;
        }

        c |= 0x20;
        digit = c - 'a';
        /* values below 'a' become large unsigned integers */

        if (digit < 6) {
            group += 10 + digit;
            continue;
        }

        return NXT_ERROR;
    }

    if (nibbles == 0 && zero_start == NULL) {
        return NXT_ERROR;
    }

    *addr++ = (u_char) (group >> 8);
    *addr++ = (u_char) (group & 0xFF);
    groups_left--;

    if (groups_left != 0) {

        if (zero_start != NULL) {

            /* moving part before consecutive zero groups to the end */

            groups_left *= 2;
            src = addr - 1;
            dst = src + groups_left;

            while (src >= zero_start) {
                *dst-- = *src--;
            }

            nxt_memzero(zero_start, groups_left);

            return NXT_OK;
        }

    } else {
        if (zero_start == NULL) {
            return NXT_OK;
        }
    }

    return NXT_ERROR;
}

#endif
