/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 *
 * This software is dual-licensed: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. For the terms of this
 * license, see <http://www.gnu.org/licenses/>.
 *
 * You are free to use this software under the terms of the GNU General
 * Public License, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * Alternatively, you can license this software under a commercial
 * license, as set out in <https://www.cesanta.com/license>.
 */

#include "common/cs_time.h"
#include "mg_dns.h"
#include "mg_internal.h"
#include "mg_resolv.h"
#include "mg_util.h"

#define MG_MAX_HOST_LEN 200

#define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \
  memcpy(dst, src, sizeof(*dst));

/* Which flags can be pre-set by the user at connection creation time. */
#define _MG_ALLOWED_CONNECT_FLAGS_MASK                                   \
  (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \
   MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG | MG_F_ENABLE_BROADCAST)
/* Which flags should be modifiable by user's callbacks. */
#define _MG_CALLBACK_MODIFIABLE_FLAGS_MASK                               \
  (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \
   MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG | MG_F_SEND_AND_CLOSE |        \
   MG_F_CLOSE_IMMEDIATELY | MG_F_IS_WEBSOCKET | MG_F_DELETE_CHUNK)

#ifndef intptr_t
#define intptr_t long
#endif

MG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c) {
  DBG(("%p %p", mgr, c));
  c->mgr = mgr;
  c->next = mgr->active_connections;
  mgr->active_connections = c;
  c->prev = NULL;
  if (c->next != NULL) c->next->prev = c;
  if (c->sock != INVALID_SOCKET) {
    c->iface->vtable->add_conn(c);
  }
}

MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {
  if (conn->prev == NULL) conn->mgr->active_connections = conn->next;
  if (conn->prev) conn->prev->next = conn->next;
  if (conn->next) conn->next->prev = conn->prev;
  conn->prev = conn->next = NULL;
  conn->iface->vtable->remove_conn(conn);
}

MG_INTERNAL void mg_call(struct mg_connection *nc,
                         mg_event_handler_t ev_handler, void *user_data, int ev,
                         void *ev_data) {
  static int nesting_level = 0;
  nesting_level++;
  if (ev_handler == NULL) {
    /*
     * If protocol handler is specified, call it. Otherwise, call user-specified
     * event handler.
     */
    ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;
  }
  if (ev != MG_EV_POLL) {
    DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc,
         ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags,
         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
  }

#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
  if (nc->mgr->hexdump_file != NULL && ev != MG_EV_POLL && ev != MG_EV_RECV &&
      ev != MG_EV_SEND /* handled separately */) {
    mg_hexdump_connection(nc, nc->mgr->hexdump_file, NULL, 0, ev);
  }
#endif
  if (ev_handler != NULL) {
    unsigned long flags_before = nc->flags;
    size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
    ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));
    recved = (recv_mbuf_before - nc->recv_mbuf.len);
    /* Prevent user handler from fiddling with system flags. */
    if (ev_handler == nc->handler && nc->flags != flags_before) {
      nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
                  (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
    }
    /* It's important to not double-count recved bytes, and since mg_call can be
     * called recursively (e.g. proto_handler invokes user handler), we keep
     * track of recursion and only report received bytes at the top level. */
    if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) {
      nc->iface->vtable->recved(nc, recved);
    }
  }
  if (ev != MG_EV_POLL) {
    DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc,
         ev_handler == nc->handler ? "user" : "proto", nc->flags,
         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
  }
  nesting_level--;
#if !MG_ENABLE_CALLBACK_USERDATA
  (void) user_data;
#endif
}

void mg_if_timer(struct mg_connection *c, double now) {
  if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
    double old_value = c->ev_timer_time;
    c->ev_timer_time = 0;
    mg_call(c, NULL, c->user_data, MG_EV_TIMER, &old_value);
  }
}

void mg_if_poll(struct mg_connection *nc, time_t now) {
  if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now);
  }
}

void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
  if (destroy_if) conn->iface->vtable->destroy_conn(conn);
  if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
    conn->proto_data_destructor(conn->proto_data);
  }
#if MG_ENABLE_SSL
  mg_ssl_if_conn_free(conn);
#endif
  mbuf_free(&conn->recv_mbuf);
  mbuf_free(&conn->send_mbuf);

  memset(conn, 0, sizeof(*conn));
  MG_FREE(conn);
}

void mg_close_conn(struct mg_connection *conn) {
  DBG(("%p %lu %d", conn, conn->flags, conn->sock));
#if MG_ENABLE_SSL
  if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
    mg_ssl_if_conn_close_notify(conn);
  }
#endif
  mg_remove_conn(conn);
  conn->iface->vtable->destroy_conn(conn);
  mg_call(conn, NULL, conn->user_data, MG_EV_CLOSE, NULL);
  mg_destroy_conn(conn, 0 /* destroy_if */);
}

void mg_mgr_init(struct mg_mgr *m, void *user_data) {
  struct mg_mgr_init_opts opts;
  memset(&opts, 0, sizeof(opts));
  mg_mgr_init_opt(m, user_data, opts);
}

void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
                     struct mg_mgr_init_opts opts) {
  memset(m, 0, sizeof(*m));
#if MG_ENABLE_BROADCAST
  m->ctl[0] = m->ctl[1] = INVALID_SOCKET;
#endif
  m->user_data = user_data;

#ifdef _WIN32
  {
    WSADATA data;
    WSAStartup(MAKEWORD(2, 2), &data);
  }
#elif defined(__unix__)
  /* Ignore SIGPIPE signal, so if client cancels the request, it
   * won't kill the whole process. */
  signal(SIGPIPE, SIG_IGN);
#endif

#if MG_ENABLE_SSL
  {
    static int init_done;
    if (!init_done) {
      mg_ssl_if_init();
      init_done++;
    }
  }
#endif
  {
    int i;
    if (opts.num_ifaces == 0) {
      opts.num_ifaces = mg_num_ifaces;
      opts.ifaces = mg_ifaces;
    }
    if (opts.main_iface != NULL) {
      opts.ifaces[MG_MAIN_IFACE] = opts.main_iface;
    }
    m->num_ifaces = opts.num_ifaces;
    m->ifaces =
        (struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces);
    for (i = 0; i < mg_num_ifaces; i++) {
      m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m);
      m->ifaces[i]->vtable->init(m->ifaces[i]);
    }
  }
  if (opts.nameserver != NULL) {
    m->nameserver = strdup(opts.nameserver);
  }
  DBG(("=================================="));
  DBG(("init mgr=%p", m));
}

void mg_mgr_free(struct mg_mgr *m) {
  struct mg_connection *conn, *tmp_conn;

  DBG(("%p", m));
  if (m == NULL) return;
  /* Do one last poll, see https://github.com/cesanta/mongoose/issues/286 */
  mg_mgr_poll(m, 0);

#if MG_ENABLE_BROADCAST
  if (m->ctl[0] != INVALID_SOCKET) closesocket(m->ctl[0]);
  if (m->ctl[1] != INVALID_SOCKET) closesocket(m->ctl[1]);
  m->ctl[0] = m->ctl[1] = INVALID_SOCKET;
#endif

  for (conn = m->active_connections; conn != NULL; conn = tmp_conn) {
    tmp_conn = conn->next;
    mg_close_conn(conn);
  }

  {
    int i;
    for (i = 0; i < m->num_ifaces; i++) {
      m->ifaces[i]->vtable->free(m->ifaces[i]);
      MG_FREE(m->ifaces[i]);
    }
    MG_FREE(m->ifaces);
  }

  MG_FREE((char *) m->nameserver);
}

time_t mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
  int i;
  time_t now = 0; /* oh GCC, seriously ? */

  if (m->num_ifaces == 0) {
    LOG(LL_ERROR, ("cannot poll: no interfaces"));
    return 0;
  }

  for (i = 0; i < m->num_ifaces; i++) {
    now = m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
  }
  return now;
}

int mg_vprintf(struct mg_connection *nc, const char *fmt, va_list ap) {
  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
  int len;

  if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
    mg_send(nc, buf, len);
  }
  if (buf != mem && buf != NULL) {
    MG_FREE(buf); /* LCOV_EXCL_LINE */
  }               /* LCOV_EXCL_LINE */

  return len;
}

int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
  int len;
  va_list ap;
  va_start(ap, fmt);
  len = mg_vprintf(conn, fmt, ap);
  va_end(ap);
  return len;
}

#if MG_ENABLE_SYNC_RESOLVER
/* TODO(lsm): use non-blocking resolver */
static int mg_resolve2(const char *host, struct in_addr *ina) {
#if MG_ENABLE_GETADDRINFO
  int rv = 0;
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_in *h = NULL;
  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  if ((rv = getaddrinfo(host, NULL, NULL, &servinfo)) != 0) {
    DBG(("getaddrinfo(%s) failed: %s", host, strerror(mg_get_errno())));
    return 0;
  }
  for (p = servinfo; p != NULL; p = p->ai_next) {
    memcpy(&h, &p->ai_addr, sizeof(struct sockaddr_in *));
    memcpy(ina, &h->sin_addr, sizeof(ina));
  }
  freeaddrinfo(servinfo);
  return 1;
#else
  struct hostent *he;
  if ((he = gethostbyname(host)) == NULL) {
    DBG(("gethostbyname(%s) failed: %s", host, strerror(mg_get_errno())));
  } else {
    memcpy(ina, he->h_addr_list[0], sizeof(*ina));
    return 1;
  }
  return 0;
#endif /* MG_ENABLE_GETADDRINFO */
}

int mg_resolve(const char *host, char *buf, size_t n) {
  struct in_addr ad;
  return mg_resolve2(host, &ad) ? snprintf(buf, n, "%s", inet_ntoa(ad)) : 0;
}
#endif /* MG_ENABLE_SYNC_RESOLVER */

MG_INTERNAL struct mg_connection *mg_create_connection_base(
    struct mg_mgr *mgr, mg_event_handler_t callback,
    struct mg_add_sock_opts opts) {
  struct mg_connection *conn;

  if ((conn = (struct mg_connection *) MG_CALLOC(1, sizeof(*conn))) != NULL) {
    conn->sock = INVALID_SOCKET;
    conn->handler = callback;
    conn->mgr = mgr;
    conn->last_io_time = (time_t) mg_time();
    conn->iface =
        (opts.iface != NULL ? opts.iface : mgr->ifaces[MG_MAIN_IFACE]);
    conn->flags = opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;
    conn->user_data = opts.user_data;
    /*
     * SIZE_MAX is defined as a long long constant in
     * system headers on some platforms and so it
     * doesn't compile with pedantic ansi flags.
     */
    conn->recv_mbuf_limit = ~0;
  } else {
    MG_SET_PTRPTR(opts.error_string, "failed to create connection");
  }

  return conn;
}

MG_INTERNAL struct mg_connection *mg_create_connection(
    struct mg_mgr *mgr, mg_event_handler_t callback,
    struct mg_add_sock_opts opts) {
  struct mg_connection *conn = mg_create_connection_base(mgr, callback, opts);

  if (conn != NULL && !conn->iface->vtable->create_conn(conn)) {
    MG_FREE(conn);
    conn = NULL;
  }
  if (conn == NULL) {
    MG_SET_PTRPTR(opts.error_string, "failed to init connection");
  }

  return conn;
}

/*
 * Address format: [PROTO://][HOST]:PORT
 *
 * HOST could be IPv4/IPv6 address or a host name.
 * `host` is a destination buffer to hold parsed HOST part. Should be at least
 * MG_MAX_HOST_LEN bytes long.
 * `proto` is a returned socket type, either SOCK_STREAM or SOCK_DGRAM
 *
 * Return:
 *   -1   on parse error
 *    0   if HOST needs DNS lookup
 *   >0   length of the address string
 */
MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
                                 int *proto, char *host, size_t host_len) {
  unsigned int a, b, c, d, port = 0;
  int ch, len = 0;
#if MG_ENABLE_IPV6
  char buf[100];
#endif

  /*
   * MacOS needs that. If we do not zero it, subsequent bind() will fail.
   * Also, all-zeroes in the socket address means binding to all addresses
   * for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
   */
  memset(sa, 0, sizeof(*sa));
  sa->sin.sin_family = AF_INET;

  *proto = SOCK_STREAM;

  if (strncmp(str, "udp://", 6) == 0) {
    str += 6;
    *proto = SOCK_DGRAM;
  } else if (strncmp(str, "tcp://", 6) == 0) {
    str += 6;
  }

  if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
    /* Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 */
    sa->sin.sin_addr.s_addr =
        htonl(((uint32_t) a << 24) | ((uint32_t) b << 16) | c << 8 | d);
    sa->sin.sin_port = htons((uint16_t) port);
#if MG_ENABLE_IPV6
  } else if (sscanf(str, "[%99[^]]]:%u%n", buf, &port, &len) == 2 &&
             inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {
    /* IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 */
    sa->sin6.sin6_family = AF_INET6;
    sa->sin.sin_port = htons((uint16_t) port);
#endif
#if MG_ENABLE_ASYNC_RESOLVER
  } else if (strlen(str) < host_len &&
             sscanf(str, "%[^ :]:%u%n", host, &port, &len) == 2) {
    sa->sin.sin_port = htons((uint16_t) port);
    if (mg_resolve_from_hosts_file(host, sa) != 0) {
      /*
       * if resolving from hosts file failed and the host
       * we are trying to resolve is `localhost` - we should
       * try to resolve it using `gethostbyname` and do not try
       * to resolve it via DNS server if gethostbyname has failed too
       */
      if (mg_ncasecmp(host, "localhost", 9) != 0) {
        return 0;
      }

#if MG_ENABLE_SYNC_RESOLVER
      if (!mg_resolve2(host, &sa->sin.sin_addr)) {
        return -1;
      }
#else
      return -1;
#endif
    }
#endif
  } else if (sscanf(str, ":%u%n", &port, &len) == 1 ||
             sscanf(str, "%u%n", &port, &len) == 1) {
    /* If only port is specified, bind to IPv4, INADDR_ANY */
    sa->sin.sin_port = htons((uint16_t) port);
  } else {
    return -1;
  }

  /* Required for MG_ENABLE_ASYNC_RESOLVER=0 */
  (void) host;
  (void) host_len;

  ch = str[len]; /* Character that follows the address */
  return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1;
}

struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
  struct mg_add_sock_opts opts;
  struct mg_connection *nc;
  memset(&opts, 0, sizeof(opts));
  nc = mg_create_connection(lc->mgr, lc->handler, opts);
  if (nc == NULL) return NULL;
  nc->listener = lc;
  nc->proto_handler = lc->proto_handler;
  nc->user_data = lc->user_data;
  nc->recv_mbuf_limit = lc->recv_mbuf_limit;
  nc->iface = lc->iface;
  if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL;
  mg_add_conn(nc->mgr, nc);
  DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
  return nc;
}

void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
                         size_t sa_len) {
  (void) sa_len;
  nc->sa = *sa;
  mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
}

void mg_send(struct mg_connection *nc, const void *buf, int len) {
  nc->last_io_time = (time_t) mg_time();
  if (nc->flags & MG_F_UDP) {
    nc->iface->vtable->udp_send(nc, buf, len);
  } else {
    nc->iface->vtable->tcp_send(nc, buf, len);
  }
}

void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
  DBG(("%p %d", nc, num_sent));
#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
    char *buf = nc->send_mbuf.buf;
    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND);
  }
#endif
  if (num_sent < 0) {
    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
  } else {
    mbuf_remove(&nc->send_mbuf, num_sent);
    mbuf_trim(&nc->send_mbuf);
  }
  mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent);
}

MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
                                int own) {
  DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));

#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV);
  }
#endif

  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
    DBG(("%p discarded %d bytes", nc, len));
    /*
     * This connection will not survive next poll. Do not deliver events,
     * send data to /dev/null without acking.
     */
    if (own) {
      MG_FREE(buf);
    }
    return;
  }
  nc->last_io_time = (time_t) mg_time();
  if (!own) {
    mbuf_append(&nc->recv_mbuf, buf, len);
  } else if (nc->recv_mbuf.len == 0) {
    /* Adopt buf as recv_mbuf's backing store. */
    mbuf_free(&nc->recv_mbuf);
    nc->recv_mbuf.buf = (char *) buf;
    nc->recv_mbuf.size = nc->recv_mbuf.len = len;
  } else {
    mbuf_append(&nc->recv_mbuf, buf, len);
    MG_FREE(buf);
  }
  mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len);
}

void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) {
  mg_recv_common(nc, buf, len, own);
}

void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
                       union socket_address *sa, size_t sa_len) {
  assert(nc->flags & MG_F_UDP);
  DBG(("%p %u", nc, (unsigned int) len));
  if (nc->flags & MG_F_LISTENING) {
    struct mg_connection *lc = nc;
    /*
     * Do we have an existing connection for this source?
     * This is very inefficient for long connection lists.
     */
    for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {
      if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) {
        break;
      }
    }
    if (nc == NULL) {
      struct mg_add_sock_opts opts;
      memset(&opts, 0, sizeof(opts));
      /* Create fake connection w/out sock initialization */
      nc = mg_create_connection_base(lc->mgr, lc->handler, opts);
      if (nc != NULL) {
        nc->sock = lc->sock;
        nc->listener = lc;
        nc->sa = *sa;
        nc->proto_handler = lc->proto_handler;
        nc->user_data = lc->user_data;
        nc->recv_mbuf_limit = lc->recv_mbuf_limit;
        nc->flags = MG_F_UDP;
        /*
         * Long-lived UDP "connections" i.e. interactions that involve more
         * than one request and response are rare, most are transactional:
         * response is sent and the "connection" is closed. Or - should be.
         * But users (including ourselves) tend to forget about that part,
         * because UDP is connectionless and one does not think about
         * processing a UDP request as handling a connection that needs to be
         * closed. Thus, we begin with SEND_AND_CLOSE flag set, which should
         * be a reasonable default for most use cases, but it is possible to
         * turn it off the connection should be kept alive after processing.
         */
        nc->flags |= MG_F_SEND_AND_CLOSE;
        mg_add_conn(lc->mgr, nc);
        mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
      } else {
        DBG(("OOM"));
        /* No return here, we still need to drop on the floor */
      }
    }
  }
  if (nc != NULL) {
    mg_recv_common(nc, buf, len, 1);
  } else {
    /* Drop on the floor. */
    MG_FREE(buf);
  }
}

/*
 * Schedules an async connect for a resolved address and proto.
 * Called from two places: `mg_connect_opt()` and from async resolver.
 * When called from the async resolver, it must trigger `MG_EV_CONNECT` event
 * with a failure flag to indicate connection failure.
 */
MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
                                                int proto,
                                                union socket_address *sa) {
  DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
       inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));

  nc->flags |= MG_F_CONNECTING;
  if (proto == SOCK_DGRAM) {
    nc->iface->vtable->connect_udp(nc);
  } else {
    nc->iface->vtable->connect_tcp(nc, sa);
  }
  mg_add_conn(nc->mgr, nc);
  return nc;
}

void mg_if_connect_cb(struct mg_connection *nc, int err) {
  DBG(("%p connect, err=%d", nc, err));
  nc->flags &= ~MG_F_CONNECTING;
  if (err != 0) {
    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
  }
  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
}

#if MG_ENABLE_ASYNC_RESOLVER
/*
 * Callback for the async resolver on mg_connect_opt() call.
 * Main task of this function is to trigger MG_EV_CONNECT event with
 *    either failure (and dealloc the connection)
 *    or success (and proceed with connect()
 */
static void resolve_cb(struct mg_dns_message *msg, void *data,
                       enum mg_resolve_err e) {
  struct mg_connection *nc = (struct mg_connection *) data;
  int i;
  int failure = -1;

  nc->flags &= ~MG_F_RESOLVING;
  if (msg != NULL) {
    /*
     * Take the first DNS A answer and run...
     */
    for (i = 0; i < msg->num_answers; i++) {
      if (msg->answers[i].rtype == MG_DNS_A_RECORD) {
        /*
         * Async resolver guarantees that there is at least one answer.
         * TODO(lsm): handle IPv6 answers too
         */
        mg_dns_parse_record_data(msg, &msg->answers[i], &nc->sa.sin.sin_addr,
                                 4);
        mg_do_connect(nc, nc->flags & MG_F_UDP ? SOCK_DGRAM : SOCK_STREAM,
                      &nc->sa);
        return;
      }
    }
  }

  if (e == MG_RESOLVE_TIMEOUT) {
    double now = mg_time();
    mg_call(nc, NULL, nc->user_data, MG_EV_TIMER, &now);
  }

  /*
   * If we get there was no MG_DNS_A_RECORD in the answer
   */
  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &failure);
  mg_call(nc, NULL, nc->user_data, MG_EV_CLOSE, NULL);
  mg_destroy_conn(nc, 1 /* destroy_if */);
}
#endif

struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
                                 MG_CB(mg_event_handler_t callback,
                                       void *user_data)) {
  struct mg_connect_opts opts;
  memset(&opts, 0, sizeof(opts));
  return mg_connect_opt(mgr, address, MG_CB(callback, user_data), opts);
}

struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
                                     MG_CB(mg_event_handler_t callback,
                                           void *user_data),
                                     struct mg_connect_opts opts) {
  struct mg_connection *nc = NULL;
  int proto, rc;
  struct mg_add_sock_opts add_sock_opts;
  char host[MG_MAX_HOST_LEN];

  MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);

  if ((nc = mg_create_connection(mgr, callback, add_sock_opts)) == NULL) {
    return NULL;
  }

  if ((rc = mg_parse_address(address, &nc->sa, &proto, host, sizeof(host))) <
      0) {
    /* Address is malformed */
    MG_SET_PTRPTR(opts.error_string, "cannot parse address");
    mg_destroy_conn(nc, 1 /* destroy_if */);
    return NULL;
  }

  nc->flags |= opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;
  nc->flags |= (proto == SOCK_DGRAM) ? MG_F_UDP : 0;
#if MG_ENABLE_CALLBACK_USERDATA
  nc->user_data = user_data;
#else
  nc->user_data = opts.user_data;
#endif

#if MG_ENABLE_SSL
  DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
       (opts.ssl_key ? opts.ssl_key : "-"),
       (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));

  if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL ||
      opts.ssl_psk_identity != NULL) {
    const char *err_msg = NULL;
    struct mg_ssl_if_conn_params params;
    if (nc->flags & MG_F_UDP) {
      MG_SET_PTRPTR(opts.error_string, "SSL for UDP is not supported");
      mg_destroy_conn(nc, 1 /* destroy_if */);
      return NULL;
    }
    memset(&params, 0, sizeof(params));
    params.cert = opts.ssl_cert;
    params.key = opts.ssl_key;
    params.ca_cert = opts.ssl_ca_cert;
    params.cipher_suites = opts.ssl_cipher_suites;
    params.psk_identity = opts.ssl_psk_identity;
    params.psk_key = opts.ssl_psk_key;
    if (opts.ssl_ca_cert != NULL) {
      if (opts.ssl_server_name != NULL) {
        if (strcmp(opts.ssl_server_name, "*") != 0) {
          params.server_name = opts.ssl_server_name;
        }
      } else if (rc == 0) { /* If it's a DNS name, use host. */
        params.server_name = host;
      }
    }
    if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {
      MG_SET_PTRPTR(opts.error_string, err_msg);
      mg_destroy_conn(nc, 1 /* destroy_if */);
      return NULL;
    }
    nc->flags |= MG_F_SSL;
  }
#endif /* MG_ENABLE_SSL */

  if (rc == 0) {
#if MG_ENABLE_ASYNC_RESOLVER
    /*
     * DNS resolution is required for host.
     * mg_parse_address() fills port in nc->sa, which we pass to resolve_cb()
     */
    struct mg_connection *dns_conn = NULL;
    struct mg_resolve_async_opts o;
    memset(&o, 0, sizeof(o));
    o.dns_conn = &dns_conn;
    o.nameserver = opts.nameserver;
    if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc,
                             o) != 0) {
      MG_SET_PTRPTR(opts.error_string, "cannot schedule DNS lookup");
      mg_destroy_conn(nc, 1 /* destroy_if */);
      return NULL;
    }
    nc->priv_2 = dns_conn;
    nc->flags |= MG_F_RESOLVING;
    return nc;
#else
    MG_SET_PTRPTR(opts.error_string, "Resolver is disabled");
    mg_destroy_conn(nc, 1 /* destroy_if */);
    return NULL;
#endif
  } else {
    /* Address is parsed and resolved to IP. proceed with connect() */
    return mg_do_connect(nc, proto, &nc->sa);
  }
}

struct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,
                              MG_CB(mg_event_handler_t event_handler,
                                    void *user_data)) {
  struct mg_bind_opts opts;
  memset(&opts, 0, sizeof(opts));
  return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);
}

struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
                                  MG_CB(mg_event_handler_t callback,
                                        void *user_data),
                                  struct mg_bind_opts opts) {
  union socket_address sa;
  struct mg_connection *nc = NULL;
  int proto, rc;
  struct mg_add_sock_opts add_sock_opts;
  char host[MG_MAX_HOST_LEN];

#if MG_ENABLE_CALLBACK_USERDATA
  opts.user_data = user_data;
#endif

  if (callback == NULL) {
    MG_SET_PTRPTR(opts.error_string, "handler is required");
    return NULL;
  }

  MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);

  if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
    MG_SET_PTRPTR(opts.error_string, "cannot parse address");
    return NULL;
  }

  nc = mg_create_connection(mgr, callback, add_sock_opts);
  if (nc == NULL) {
    return NULL;
  }

  nc->sa = sa;
  nc->flags |= MG_F_LISTENING;
  if (proto == SOCK_DGRAM) nc->flags |= MG_F_UDP;

#if MG_ENABLE_SSL
  DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
       (opts.ssl_key ? opts.ssl_key : "-"),
       (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));

  if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {
    const char *err_msg = NULL;
    struct mg_ssl_if_conn_params params;
    if (nc->flags & MG_F_UDP) {
      MG_SET_PTRPTR(opts.error_string, "SSL for UDP is not supported");
      mg_destroy_conn(nc, 1 /* destroy_if */);
      return NULL;
    }
    memset(&params, 0, sizeof(params));
    params.cert = opts.ssl_cert;
    params.key = opts.ssl_key;
    params.ca_cert = opts.ssl_ca_cert;
    params.cipher_suites = opts.ssl_cipher_suites;
    if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {
      MG_SET_PTRPTR(opts.error_string, err_msg);
      mg_destroy_conn(nc, 1 /* destroy_if */);
      return NULL;
    }
    nc->flags |= MG_F_SSL;
  }
#endif /* MG_ENABLE_SSL */

  if (nc->flags & MG_F_UDP) {
    rc = nc->iface->vtable->listen_udp(nc, &nc->sa);
  } else {
    rc = nc->iface->vtable->listen_tcp(nc, &nc->sa);
  }
  if (rc != 0) {
    DBG(("Failed to open listener: %d", rc));
    MG_SET_PTRPTR(opts.error_string, "failed to open listener");
    mg_destroy_conn(nc, 1 /* destroy_if */);
    return NULL;
  }
  mg_add_conn(nc->mgr, nc);

  return nc;
}

struct mg_connection *mg_next(struct mg_mgr *s, struct mg_connection *conn) {
  return conn == NULL ? s->active_connections : conn->next;
}

#if MG_ENABLE_BROADCAST
void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,
                  size_t len) {
  struct ctl_msg ctl_msg;

  /*
   * Mongoose manager has a socketpair, `struct mg_mgr::ctl`,
   * where `mg_broadcast()` pushes the message.
   * `mg_mgr_poll()` wakes up, reads a message from the socket pair, and calls
   * specified callback for each connection. Thus the callback function executes
   * in event manager thread.
   */
  if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&
      len < sizeof(ctl_msg.message)) {
    size_t dummy;

    ctl_msg.callback = cb;
    memcpy(ctl_msg.message, data, len);
    dummy = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,
                         offsetof(struct ctl_msg, message) + len, 0);
    dummy = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);
    (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
  }
}
#endif /* MG_ENABLE_BROADCAST */

static int isbyte(int n) {
  return n >= 0 && n <= 255;
}

static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
  int n, a, b, c, d, slash = 32, len = 0;

  if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
       sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
      isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 &&
      slash < 33) {
    len = n;
    *net =
        ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d;
    *mask = slash ? 0xffffffffU << (32 - slash) : 0;
  }

  return len;
}

int mg_check_ip_acl(const char *acl, uint32_t remote_ip) {
  int allowed, flag;
  uint32_t net, mask;
  struct mg_str vec;

  /* If any ACL is set, deny by default */
  allowed = (acl == NULL || *acl == '\0') ? '+' : '-';

  while ((acl = mg_next_comma_list_entry(acl, &vec, NULL)) != NULL) {
    flag = vec.p[0];
    if ((flag != '+' && flag != '-') ||
        parse_net(&vec.p[1], &net, &mask) == 0) {
      return -1;
    }

    if (net == (remote_ip & mask)) {
      allowed = flag;
    }
  }

  DBG(("%08x %c", (unsigned int) remote_ip, allowed));
  return allowed == '+';
}

/* Move data from one connection to another */
void mg_forward(struct mg_connection *from, struct mg_connection *to) {
  mg_send(to, from->recv_mbuf.buf, from->recv_mbuf.len);
  mbuf_remove(&from->recv_mbuf, from->recv_mbuf.len);
}

double mg_set_timer(struct mg_connection *c, double timestamp) {
  double result = c->ev_timer_time;
  c->ev_timer_time = timestamp;
  /*
   * If this connection is resolving, it's not in the list of active
   * connections, so not processed yet. It has a DNS resolver connection
   * linked to it. Set up a timer for the DNS connection.
   */
  DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),
       (unsigned long) timestamp));
  if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
    ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
  }
  return result;
}

void mg_sock_set(struct mg_connection *nc, sock_t sock) {
  if (sock != INVALID_SOCKET) {
    nc->iface->vtable->sock_set(nc, sock);
  }
}

void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
                         union socket_address *sa) {
  nc->iface->vtable->get_conn_addr(nc, remote, sa);
}

struct mg_connection *mg_add_sock_opt(struct mg_mgr *s, sock_t sock,
                                      MG_CB(mg_event_handler_t callback,
                                            void *user_data),
                                      struct mg_add_sock_opts opts) {
#if MG_ENABLE_CALLBACK_USERDATA
  opts.user_data = user_data;
#endif

  struct mg_connection *nc = mg_create_connection_base(s, callback, opts);
  if (nc != NULL) {
    mg_sock_set(nc, sock);
    mg_add_conn(nc->mgr, nc);
  }
  return nc;
}

struct mg_connection *mg_add_sock(struct mg_mgr *s, sock_t sock,
                                  MG_CB(mg_event_handler_t callback,
                                        void *user_data)) {
  struct mg_add_sock_opts opts;
  memset(&opts, 0, sizeof(opts));
  return mg_add_sock_opt(s, sock, MG_CB(callback, user_data), opts);
}

double mg_time(void) {
  return cs_time();
}
