/* Copyright 2010, 2011, 2012 Tony Garnock-Jones <tonygarnockjones@gmail.com>.
 *
 * This file is part of Hop.
 *
 * Hop is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Hop is distributed in the hope that it will be useful, 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with Hop.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <time.h>
#include <sys/time.h>

#include <stdarg.h>
#include <assert.h>

typedef unsigned char u_char;
#include <event.h>

#include "cmsg_private.h"
#include "relay.h"

static struct event accept_event;

void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin) {
  unsigned char *addr = (unsigned char *) &sin->sin_addr.s_addr;
  struct hostent *h = gethostbyaddr(addr, 4, AF_INET);

  if (h == NULL) {
    snprintf(namebuf, buflen, "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]);
  } else {
    snprintf(namebuf, buflen, "%s", h->h_name);
  }
}

void endpoint_name(struct sockaddr_in const *peername, cmsg_bytes_t result) {
  char name[256];
  get_addr_name(name, sizeof(name), peername);
  snprintf((char *) result.bytes, result.len, "%s:%d", name, ntohs(peername->sin_port));
}

static void accept_connection(int servfd, short what, void *arg) {
  struct sockaddr_in s;
  socklen_t addrlen = sizeof(s);
  int fd = accept(servfd, (struct sockaddr *) &s, &addrlen);

  if (fd == -1) {
    if (errno != EAGAIN && errno != EINTR) {
      warn("accept: errno %d (%s)\n", errno, strerror(errno));
    }
    return;
  }

  {
    int i = 1;
    ICHECK(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)), "setsockopt TCP_NODELAY");
  }

  start_relay(&s, fd);
}

void start_net(int listen_port) {
  int servfd = socket(AF_INET, SOCK_STREAM, 0);
  struct sockaddr_in s;

  if (servfd < 0) {
    die("Could not open listen socket.\n");
  }

  s.sin_family = AF_INET;
  s.sin_addr.s_addr = htonl(INADDR_ANY);
  s.sin_port = htons(listen_port);

  {
    int i = 1;
    setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); // don't care if this fails
  }

  if (bind(servfd, (struct sockaddr *) &s, sizeof(s)) < 0) {
    die("Could not bind listen socket.\n");
  }

  if (listen(servfd, 5) < 0) {
    int savedErrno = errno;
    die("Could not listen on listen socket (errno %d: %s).\n",
	savedErrno, strerror(savedErrno));
  }

  event_set(&accept_event, servfd, EV_READ | EV_PERSIST, accept_connection, NULL);
  if (event_add(&accept_event, NULL) == -1) {
    die("Could not add accept_event.");
  }

  info("Accepting connections on port %d.\n", listen_port);
}
