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

#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <poll.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <stdio.h>
#include <signal.h>

#include "os/mynewt.h"
#include "mn_socket/mn_socket.h"
#include "mn_socket/mn_socket_ops.h"
#include "native_sockets/native_sock.h"

#include "native_sock_priv.h"

OS_TASK_STACK_DEFINE(native_sock_stack, MYNEWT_VAL(NATIVE_SOCKETS_STACK_SZ));

static struct native_sock {
    struct mn_socket ns_sock;
    int ns_fd;
    unsigned int ns_connect:1;  /* Non-blocking connect in progress. */
    unsigned int ns_poll:1;
    unsigned int ns_listen:1;
    uint8_t ns_type;
    uint8_t ns_pf;
    struct os_sem ns_sem;
    STAILQ_HEAD(, os_mbuf_pkthdr) ns_rx;
    struct os_mbuf *ns_tx;
} native_socks[MYNEWT_VAL(NATIVE_SOCKETS_MAX)];

static struct native_sock_state {
    struct pollfd poll_fds[MYNEWT_VAL(NATIVE_SOCKETS_MAX)];
    int poll_fd_cnt;
    struct os_mutex mtx;
    struct os_task task;
} native_sock_state;

static const struct mn_socket_ops native_sock_ops = {
    .mso_create = native_sock_create,
    .mso_close = native_sock_close,

    .mso_bind = native_sock_bind,
    .mso_connect = native_sock_connect,
    .mso_listen = native_sock_listen,

    .mso_sendto = native_sock_sendto,
    .mso_recvfrom = native_sock_recvfrom,

    .mso_getsockopt = native_sock_getsockopt,
    .mso_setsockopt = native_sock_setsockopt,

    .mso_getsockname = native_sock_getsockname,
    .mso_getpeername = native_sock_getpeername,

    .mso_itf_getnext = native_sock_itf_getnext,
    .mso_itf_addr_getnext = native_sock_itf_addr_getnext
};

static struct native_sock *
native_get_sock(void)
{
    int i;
    struct native_sock *ns;

    for (i = 0; i < MYNEWT_VAL(NATIVE_SOCKETS_MAX); i++) {
        if (native_socks[i].ns_fd < 0) {
            ns = &native_socks[i];
            ns->ns_poll = 0;
            ns->ns_listen = 0;
            return ns;
        }
    }
    return NULL;
}

static struct native_sock *
native_find_sock(int fd)
{
    int i;

    for (i = 0; i < MYNEWT_VAL(NATIVE_SOCKETS_MAX); i++) {
        if (native_socks[i].ns_fd == fd) {
            return &native_socks[i];
        }
    }
    return NULL;
}

static void
native_sock_poll_rebuild(struct native_sock_state *nss)
{
    struct native_sock *ns;
    int i;
    int j;

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    for (i = 0, j = 0; i < MYNEWT_VAL(NATIVE_SOCKETS_MAX); i++) {
        ns = &native_socks[i];
        if (ns->ns_fd < 0) {
            continue;
        }
        if (!ns->ns_poll) {
            continue;
        }
        nss->poll_fds[j].fd = ns->ns_fd;
        nss->poll_fds[j].events = POLLIN | POLLOUT;
        nss->poll_fds[j].revents = 0;
        j++;
    }
    nss->poll_fd_cnt = j;
    os_mutex_release(&nss->mtx);
}

int
native_sock_err_to_mn_err(int err)
{
    switch (err) {
    case 0:
        return 0;
    case EAGAIN:
    case EINPROGRESS:
        return MN_EAGAIN;
    case ENOTCONN:
        return MN_ENOTCONN;
    case ETIMEDOUT:
        return MN_ETIMEDOUT;
    case ENOMEM:
        return MN_ENOBUFS;
    case EADDRINUSE:
        return MN_EADDRINUSE;
    case EADDRNOTAVAIL:
        return MN_EADDRNOTAVAIL;
    default:
        return MN_EINVAL;
    }
}

static int
native_sock_mn_addr_to_addr(struct mn_sockaddr *ms, struct sockaddr *sa,
  int *sa_len)
{
    struct sockaddr_un *sun = (struct sockaddr_un *)sa;
    struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
    struct mn_sockaddr_un *msun = (struct mn_sockaddr_un *)ms;
    struct mn_sockaddr_in *msin = (struct mn_sockaddr_in *)ms;
    struct mn_sockaddr_in6 *msin6 = (struct mn_sockaddr_in6 *)ms;

    switch (ms->msa_family) {
    case MN_AF_INET:
        sin->sin_family = AF_INET;
#ifndef MN_LINUX
        sin->sin_len = sizeof(*sin);
#endif
        sin->sin_addr.s_addr = msin->msin_addr.s_addr;
        sin->sin_port = msin->msin_port;
        *sa_len = sizeof(*sin);
        break;
    case MN_AF_INET6:
        sin6->sin6_family = AF_INET6;
#ifndef MN_LINUX
        sin6->sin6_len = sizeof(*sin6);
#endif
        sin6->sin6_port = msin6->msin6_port;
        sin6->sin6_flowinfo = msin6->msin6_flowinfo;
        memcpy(&sin6->sin6_addr, &msin6->msin6_addr,
               sizeof(msin6->msin6_addr));
        sin6->sin6_scope_id = msin6->msin6_scope_id;
        *sa_len = sizeof(*sin6);
        break;
    case MN_AF_LOCAL:
        sun->sun_family = AF_LOCAL;
#ifndef MN_LINUX
        sun->sun_len = sizeof(*sun);
#endif
        sun->sun_path[0] = '\0';
        strncat(sun->sun_path, msun->msun_path, sizeof(sun->sun_path) - 1);
        if (strcmp(sun->sun_path, msun->msun_path) != 0) {
            /* Path too long. */
            return MN_EINVAL;
        }
        *sa_len = sizeof(*sun);
        break;
    default:
        return MN_EPROTONOSUPPORT;
    }
    return 0;
}

static int
native_sock_addr_to_mn_addr(struct sockaddr *sa, struct mn_sockaddr *ms)
{
    struct mn_sockaddr_un *msun = (struct mn_sockaddr_un *)ms;
    struct mn_sockaddr_in *msin = (struct mn_sockaddr_in *)ms;
    struct mn_sockaddr_in6 *msin6 = (struct mn_sockaddr_in6 *)ms;
    struct sockaddr_un *sun = (struct sockaddr_un *)sa;
    struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;

    switch (sa->sa_family) {
    case AF_INET:
        msin->msin_family = MN_AF_INET;
        msin->msin_len = sizeof(*msin);
        msin->msin_addr.s_addr = sin->sin_addr.s_addr;
        msin->msin_port = sin->sin_port;
        break;
    case AF_INET6:
        msin6->msin6_family = MN_AF_INET6;
        msin6->msin6_len = sizeof(*msin6);
        msin6->msin6_port = sin6->sin6_port;
        msin6->msin6_flowinfo = sin6->sin6_flowinfo;
        memcpy(&msin6->msin6_addr, &sin6->sin6_addr,
               sizeof(msin6->msin6_addr));
        msin6->msin6_scope_id = sin6->sin6_scope_id;
        break;
    case AF_LOCAL:
        msun->msun_family = MN_AF_LOCAL;
        strncpy(msun->msun_path, sun->sun_path, sizeof msun->msun_path);
        if (strcmp(msun->msun_path, sun->sun_path) != 0) {
            /* Path too long. */
            return MN_EINVAL;
        }
        break;
    default:
        return MN_EPROTONOSUPPORT;
    }
    return 0;
}

static void
native_sock_set_nonblocking(struct native_sock *ns)
{
    int rc;

    rc = fcntl(ns->ns_fd, F_SETFL, fcntl(ns->ns_fd, F_GETFL, 0) | O_NONBLOCK);
    assert(rc == 0);
}

int
native_sock_create(struct mn_socket **sp, uint8_t domain,
  uint8_t type, uint8_t proto)
{
    struct native_sock_state *nss = &native_sock_state;
    struct native_sock *ns;
    int idx;

    switch (domain) {
    case MN_PF_INET:
        domain = PF_INET;
        break;
    case MN_PF_INET6:
        domain = PF_INET6;
        break;
    case MN_PF_LOCAL:
        domain = PF_LOCAL;
        break;
    default:
        return MN_EPROTONOSUPPORT;
    }

    switch (type) {
    case MN_SOCK_DGRAM:
        type = SOCK_DGRAM;
        break;
    case MN_SOCK_STREAM:
        type = SOCK_STREAM;
        break;
    case 0:
        break;
    default:
        return MN_EPROTONOSUPPORT;
    }

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    ns = native_get_sock();
    if (!ns) {
        os_mutex_release(&nss->mtx);
        return MN_ENOBUFS;
    }
    os_sem_init(&ns->ns_sem, 0);
    idx = socket(domain, type, proto);

    ns->ns_fd = idx;
    ns->ns_pf = domain;
    ns->ns_type = type;
    native_sock_set_nonblocking(ns);

    os_mutex_release(&nss->mtx);
    if (idx < 0) {
        return MN_ENOBUFS;
    }
    *sp = &ns->ns_sock;
    return 0;
}

int
native_sock_close(struct mn_socket *s)
{
    struct native_sock_state *nss = &native_sock_state;
    struct native_sock *ns = (struct native_sock *)s;
    struct os_mbuf_pkthdr *m;

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    close(ns->ns_fd);
    ns->ns_fd = -1;

    /*
     * When socket is closed, we must free all mbufs which might be
     * queued in it.
     */
    while ((m = STAILQ_FIRST(&ns->ns_rx))) {
        STAILQ_REMOVE_HEAD(&ns->ns_rx, omp_next);
        os_mbuf_free_chain(OS_MBUF_PKTHDR_TO_MBUF(m));
    }
    os_mbuf_free_chain(ns->ns_tx);
    native_sock_poll_rebuild(nss);
    os_mutex_release(&nss->mtx);
    return 0;
}

int
native_sock_connect(struct mn_socket *s, struct mn_sockaddr *addr)
{
    struct native_sock_state *nss = &native_sock_state;
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    int rc;
    int sa_len;
    int in_progress = 0;

    rc = native_sock_mn_addr_to_addr(addr, sa, &sa_len);
    if (rc) {
        return rc;
    }
    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    rc = connect(ns->ns_fd, sa, sa_len);
    if (rc != 0) {
        if (errno == EINPROGRESS) {
            /* Non-blocking connect initiated. */
            in_progress = 1;
            ns->ns_connect = 1;
        } else {
            rc = errno;
            os_mutex_release(&nss->mtx);
            return native_sock_err_to_mn_err(rc);
        }
    }
    ns->ns_poll = 1;
    native_sock_poll_rebuild(nss);
    os_mutex_release(&nss->mtx);

    /* Indicate writability if connection fully established. */
    if (!in_progress) {
        mn_socket_writable(s, 0);
    }

    return 0;
}

int
native_sock_bind(struct mn_socket *s, struct mn_sockaddr *addr)
{
    struct native_sock_state *nss = &native_sock_state;
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_un ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    int rc;
    int sa_len;
    int val = 1;

    rc = native_sock_mn_addr_to_addr(addr, sa, &sa_len);
    if (rc) {
        return rc;
    }

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    if (ns->ns_type == SOCK_STREAM) {
        rc = setsockopt(ns->ns_fd, SOL_SOCKET, SO_REUSEADDR, &val,
          sizeof(val));
        if (rc) {
            goto err;
        }
    }
    rc = ioctl(ns->ns_fd, FIONBIO, (char *)&val);
    if (rc) {
        goto err;
    }
    if (bind(ns->ns_fd, sa, sa_len)) {
        goto err;
    }
    if (ns->ns_type == SOCK_DGRAM) {
        ns->ns_poll = 1;
        native_sock_poll_rebuild(nss);
    }
    os_mutex_release(&nss->mtx);
    return 0;
err:
    rc = errno;
    os_mutex_release(&nss->mtx);
    return native_sock_err_to_mn_err(rc);
}

int
native_sock_listen(struct mn_socket *s, uint8_t qlen)
{
    struct native_sock_state *nss = &native_sock_state;
    struct native_sock *ns = (struct native_sock *)s;
    int rc;

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    if (listen(ns->ns_fd, qlen)) {
        rc = errno;
        os_mutex_release(&nss->mtx);
        return native_sock_err_to_mn_err(rc);
    }
    ns->ns_poll = 1;
    ns->ns_listen = 1;
    native_sock_poll_rebuild(nss);
    os_mutex_release(&nss->mtx);
    return 0;
}

/*
 * TX routine for stream sockets (TCP). The data to send is pointed
 * by ns_tx.
 * Keep sending mbufs until socket says that it can't take anymore.
 * then wait for send event notification before continuing.
 */
static int
native_sock_stream_tx(struct native_sock *ns, int notify)
{
    struct native_sock_state *nss = &native_sock_state;
    struct os_mbuf *m;
    struct os_mbuf *n;
    int rc;

    rc = 0;

    os_mutex_pend(&nss->mtx, OS_TIMEOUT_NEVER);
    while (ns->ns_tx && rc == 0) {
        m = ns->ns_tx;
        n = SLIST_NEXT(m, om_next);

        errno = 0;
        rc = write(ns->ns_fd, m->om_data, m->om_len);
        if (rc == m->om_len) {
            /* Complete write. */
            ns->ns_tx = n;
            os_mbuf_free(m);
            rc = 0;
        } else if (rc != -1) {
            /* Partial write. */
            os_mbuf_adj(m, m->om_len - rc);
            rc = 0;
        } else {
            /* Error. */
            rc = errno;
            if (rc == EAGAIN) {
                rc = 0;
            } else {
                /*
                 * Socket had an error. User should close it.
                 */
                os_mbuf_free_chain(ns->ns_tx);
                ns->ns_tx = NULL;
                rc = native_sock_err_to_mn_err(rc);
            }
            break;
        }
    }
    os_mutex_release(&nss->mtx);
    if (notify) {
        mn_socket_writable(&ns->ns_sock, rc);
    }
    return rc;
}

static int
native_sock_set_tx_buf(struct native_sock *ns, struct os_mbuf *om)
{
    struct native_sock_state *nss = &native_sock_state;
    int rc;

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    if (ns->ns_tx) {
        rc = MN_EAGAIN;
    } else {
        ns->ns_tx = om;
        rc = 0;
    }
    os_mutex_release(&nss->mtx);

    return rc;
}

int
native_sock_sendto(struct mn_socket *s, struct os_mbuf *m,
  struct mn_sockaddr *addr)
{
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    uint8_t tmpbuf[MYNEWT_VAL(NATIVE_SOCKETS_MAX_UDP)];
    struct os_mbuf *o;
    int sa_len;
    int off;
    int rc;

    if (ns->ns_type == SOCK_DGRAM) {
        rc = native_sock_mn_addr_to_addr(addr, sa, &sa_len);
        if (rc) {
            return rc;
        }
        off = 0;
        for (o = m; o; o = SLIST_NEXT(o, om_next)) {
            if (off + o->om_len > sizeof(tmpbuf)) {
                return MN_ENOBUFS;
            }
            os_mbuf_copydata(o, 0, o->om_len, &tmpbuf[off]);
            off += o->om_len;
        }
        rc = sendto(ns->ns_fd, tmpbuf, off, 0, sa, sa_len);
        if (rc != off) {
            return native_sock_err_to_mn_err(errno);
        }
        os_mbuf_free_chain(m);
        return 0;
    } else {
        rc = native_sock_set_tx_buf(ns, m);
        if (rc != 0) {
            return rc;
        }

        rc = native_sock_stream_tx(ns, 0);
        return rc;
    }
}

int
native_sock_recvfrom(struct mn_socket *s, struct os_mbuf **mp,
  struct mn_sockaddr *addr)
{
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    uint8_t tmpbuf[MYNEWT_VAL(NATIVE_SOCKETS_MAX_UDP)];
    struct os_mbuf *m;
    socklen_t slen;
    int rc;

    slen = sizeof(ss);
    if (ns->ns_type == SOCK_DGRAM) {
        rc = recvfrom(ns->ns_fd, tmpbuf, sizeof(tmpbuf), 0, sa, &slen);
    } else {
        rc = getpeername(ns->ns_fd, sa, &slen);
        if (rc == 0) {
            rc = read(ns->ns_fd, tmpbuf, sizeof(tmpbuf));
        }
    }
    if (rc < 0) {
        return native_sock_err_to_mn_err(errno);
    }
    if (ns->ns_type == SOCK_STREAM && rc == 0) {
        ns->ns_poll = 0;
        native_sock_poll_rebuild(&native_sock_state);
        return MN_ECONNABORTED;
    }

    m = os_msys_get_pkthdr(rc, 0);
    if (!m) {
        return MN_ENOBUFS;
    }
    os_mbuf_copyinto(m, 0, tmpbuf, rc);
    *mp = m;
    if (addr) {
        native_sock_addr_to_mn_addr(sa, addr);
    }
    return 0;
}

int
native_sock_getsockopt(struct mn_socket *s, uint8_t level, uint8_t name,
  void *val)
{
    return MN_EPROTONOSUPPORT;
}

int
native_sock_setsockopt(struct mn_socket *s, uint8_t level, uint8_t name,
                       void *val)
{
    struct native_sock *ns = (struct native_sock *)s;
    int rc;
    uint32_t val32;
    struct ip_mreq ip_mreq;
    struct ipv6_mreq ipv6_mreq;
    void *opt;
    int opt_len;
    struct mn_mreq *mreq;

    if (level == MN_SO_LEVEL) {
        switch (name) {
        case MN_MCAST_JOIN_GROUP:
        case MN_MCAST_LEAVE_GROUP:
            mreq = val;
            if (mreq->mm_family == MN_AF_INET) {
                memset(&ip_mreq, 0, sizeof(ip_mreq));
                if (native_sock_itf_addr(mreq->mm_idx,
                                         &ip_mreq.imr_interface.s_addr)) {
                    return MN_EADDRNOTAVAIL;
                }
                ip_mreq.imr_multiaddr.s_addr = mreq->mm_addr.v4.s_addr;
                level = IPPROTO_IP;
                if (name == MN_MCAST_JOIN_GROUP) {
                    name = IP_ADD_MEMBERSHIP;
                } else {
                    name = IP_DROP_MEMBERSHIP;
                }
                opt = &ip_mreq;
                opt_len = sizeof(ip_mreq);
            } else {
                memset(&ipv6_mreq, 0, sizeof(ipv6_mreq));
                ipv6_mreq.ipv6mr_interface = mreq->mm_idx;
                memcpy(&ipv6_mreq.ipv6mr_multiaddr, &mreq->mm_addr,
                       sizeof(struct in6_addr));
                level = IPPROTO_IPV6;
                if (name == MN_MCAST_JOIN_GROUP) {
                    name = IPV6_JOIN_GROUP;
                } else {
                    name = IPV6_LEAVE_GROUP;
                }
                opt = &ipv6_mreq;
                opt_len = sizeof(ipv6_mreq);
            }

            rc = setsockopt(ns->ns_fd, level, name, opt, opt_len);
            if (rc) {
                return native_sock_err_to_mn_err(errno);
            }
            return 0;
        case MN_MCAST_IF:
            if (ns->ns_pf == AF_INET) {
                level = IPPROTO_IP;
                name = IP_MULTICAST_IF;
                rc = native_sock_itf_addr(*(int *)val, &val32);
                if (rc) {
                    return rc;
                }
            } else {
                level = IPPROTO_IPV6;
                name = IPV6_MULTICAST_IF;
                val32 = *(uint32_t *)val;
            }
            rc = setsockopt(ns->ns_fd, level, name, &val32, sizeof(val32));
            if (rc) {
                return native_sock_err_to_mn_err(errno);
            }
            return 0;

        case MN_REUSEADDR:
            name = SO_REUSEADDR;
            val32 = *(uint32_t *)val;
            rc = setsockopt(ns->ns_fd, level, name, &val32, sizeof(val32));
            if (rc) {
                return native_sock_err_to_mn_err(errno);
            }
            return 0;
        }
    }
    return MN_EPROTONOSUPPORT;
}

int
native_sock_getsockname(struct mn_socket *s, struct mn_sockaddr *addr)
{
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    socklen_t len;
    int rc;

    len = sizeof(struct sockaddr_storage);
    rc = getsockname(ns->ns_fd, sa, &len);
    if (rc) {
        return native_sock_err_to_mn_err(errno);
    }
    rc = native_sock_addr_to_mn_addr(sa, addr);
    if (rc) {
        return rc;
    }
    return 0;
}

int
native_sock_getpeername(struct mn_socket *s, struct mn_sockaddr *addr)
{
    struct native_sock *ns = (struct native_sock *)s;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    socklen_t len;
    int rc;

    len = sizeof(struct sockaddr_storage);
    rc = getpeername(ns->ns_fd, sa, &len);
    if (rc) {
        return native_sock_err_to_mn_err(errno);
    }
    rc = native_sock_addr_to_mn_addr(sa, addr);
    if (rc) {
        return rc;
    }
    return 0;
}

/*
 * XXX should do this task with SIGIO as well.
 */
static void
socket_task(void *arg)
{
    struct native_sock_state *nss = arg;
    struct native_sock *ns, *new_ns;
    struct sockaddr_storage ss;
    struct sockaddr *sa = (struct sockaddr *)&ss;
    int revents;
    int i;
    socklen_t slen;
    int sock_err;
    int rc;

    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
    while (1) {
        os_mutex_release(&nss->mtx);
        os_time_delay(os_time_ms_to_ticks32(MYNEWT_VAL(NATIVE_SOCKETS_POLL_INTERVAL_MS)));
        os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
        if (nss->poll_fd_cnt) {
            rc = poll(nss->poll_fds, nss->poll_fd_cnt, 0);
        } else {
            rc = 0;
        }
        if (rc == 0) {
            continue;
        }
        for (i = 0; i < nss->poll_fd_cnt; i++) {
            if (nss->poll_fds[i].revents == 0) {
                continue;
            }

            revents = nss->poll_fds[i].revents;
            nss->poll_fds[i].revents = 0;

            ns = native_find_sock(nss->poll_fds[i].fd);
            assert(ns);

            if (revents & POLLIN) {
                if (ns->ns_listen) {
                    new_ns = native_get_sock();
                    if (!new_ns) {
                        continue;
                    }
                    slen = sizeof(ss);
                    new_ns->ns_fd = accept(ns->ns_fd, sa, &slen);
                    if (new_ns->ns_fd < 0) {
                        continue;
                    }
                    new_ns->ns_type = ns->ns_type;
                    new_ns->ns_sock.ms_ops = &native_sock_ops;
                    native_sock_set_nonblocking(new_ns);

                    os_mutex_release(&nss->mtx);
                    if (mn_socket_newconn(&ns->ns_sock, &new_ns->ns_sock)) {
                        /*
                         * should close
                         */
                    }
                    os_mutex_pend(&nss->mtx, OS_WAIT_FOREVER);
                    new_ns->ns_poll = 1;
                    native_sock_poll_rebuild(nss);
                } else {
                    mn_socket_readable(&ns->ns_sock, 0);
                }
            }

            if (revents & POLLOUT) {
                if (ns->ns_connect) {
                    /*
                     * The connection attempt has completed.  Report whether it
                     * succeeded.
                     */
                    ns->ns_connect = 0;

                    slen = sizeof(sock_err);
                    rc = getsockopt(ns->ns_fd, SOL_SOCKET, SO_ERROR,
                                    &sock_err, &slen);
                    if (rc != 0) {
                        rc = native_sock_err_to_mn_err(errno);
                    } else if (sock_err != 0) {
                        rc = native_sock_err_to_mn_err(sock_err);
                    }
                    mn_socket_writable(&ns->ns_sock, rc);
                } else if (ns->ns_type == SOCK_STREAM && ns->ns_tx) {
                    native_sock_stream_tx(ns, 1);
                }
            }
        }
    }
}

int
native_sock_init(void)
{
    struct native_sock_state *nss = &native_sock_state;
    int i;

    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    for (i = 0; i < MYNEWT_VAL(NATIVE_SOCKETS_MAX); i++) {
        native_socks[i].ns_fd = -1;
        STAILQ_INIT(&native_socks[i].ns_rx);
    }

    os_mutex_init(&nss->mtx);
    i = os_task_init(&nss->task, "socket", socket_task, &native_sock_state,
                     MYNEWT_VAL(NATIVE_SOCKETS_PRIO), OS_WAIT_FOREVER, native_sock_stack,
                     MYNEWT_VAL(NATIVE_SOCKETS_STACK_SZ));
    if (i) {
        return -1;
    }
    i = mn_socket_ops_reg(&native_sock_ops);
    if (i) {
        return -1;
    }

    signal(SIGPIPE, SIG_IGN);

    return 0;
}
