/*
 *  Copyright 2006  Serge van den Boom <svdb@stack.nl>
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define PORT_WANT_ERRNO
#include "port.h"
#include "../netport.h"

#define LISTEN_INTERNAL
#define SOCKET_INTERNAL
#include "listen.h"

#include "resolve.h"
#include "../socket/socket.h"
#include "../netmanager/netmanager.h"
#include "libs/misc.h"
#include "libs/log.h"

#include <assert.h>
#include <errno.h>
#ifdef USE_WINSOCK
#	include <winsock2.h>
#	include <ws2tcpip.h>
#	include "../wspiapiwrap.h"
#else
#	include <netdb.h>
#endif
#include <stdlib.h>
#include <string.h>

#define DEBUG_LISTEN_REF
#ifdef DEBUG_LISTEN_REF
#	include "types.h"
#endif


static void acceptCallback(NetDescriptor *nd);
static void doListenErrorCallback(ListenState *listenState,
		const ListenError *error);


static ListenState *
ListenState_alloc(void) {
	return (ListenState *) malloc(sizeof (ListenState));
};

static void
ListenState_free(ListenState *listenState) {
	free(listenState);
}

static void
ListenState_delete(ListenState *listenState) {
	assert(listenState->nds == NULL);
	ListenState_free(listenState);
}

void
ListenState_incRef(ListenState *listenState) {
	assert(listenState->refCount < REFCOUNT_MAX);
	listenState->refCount++;
#ifdef DEBUG_LISTEN_REF
	log_add(log_Debug, "ListenState %08" PRIxPTR ": ref++ (%d)\n",
			(uintptr_t) listenState, listenState->refCount);
#endif
}

bool
ListenState_decRef(ListenState *listenState) {
	assert(listenState->refCount > 0);
	listenState->refCount--;
#ifdef DEBUG_LISTEN_REF
	log_add(log_Debug, "ListenState %08" PRIxPTR ": ref-- (%d)\n",
			(uintptr_t) listenState, listenState->refCount);
#endif
	if (listenState->refCount == 0) {
		ListenState_delete(listenState);
		return true;
	}
	return false;
}

// Decrements ref count byh 1
void
ListenState_close(ListenState *listenState) {
	if (listenState->resolveState != NULL) {
		Resolve_close(listenState->resolveState);
		listenState->resolveState = NULL;
	}
	if (listenState->nds != NULL) {
		while (listenState->numNd > 0) {
			listenState->numNd--;
			NetDescriptor_close(listenState->nds[listenState->numNd]);
		}
		free(listenState->nds);
		listenState->nds = NULL;
	}
	listenState->state = Listen_closed;
	ListenState_decRef(listenState);
}

void
ListenState_setExtra(ListenState *listenState, void *extra) {
	listenState->extra = extra;
}

void *
ListenState_getExtra(ListenState *listenState) {
	return listenState->extra;
}

static NetDescriptor *
listenPortSingle(struct ListenState *listenState, struct addrinfo *info) {
	Socket *sock;
	int bindResult;
	int listenResult;
	NetDescriptor *nd;

	sock = Socket_openNative(info->ai_family, info->ai_socktype,
			info->ai_protocol);
	if (sock == Socket_noSocket) {
		int savedErrno = errno;
		log_add(log_Error, "socket() failed: %s.\n", strerror(errno));
		errno = savedErrno;
		return NULL;
	}

	(void) Socket_setReuseAddr(sock);
			// Ignore errors; it's not a big deal.
	if (Socket_setNonBlocking(sock) == -1) {
		int savedErrno = errno;
		// Error message is already printed.
		Socket_close(sock);
		errno = savedErrno;
		return NULL;
	}
	
	bindResult = Socket_bind(sock, info->ai_addr, info->ai_addrlen);
	if (bindResult == -1) {
		int savedErrno = errno;
		if (errno == EADDRINUSE) {
#ifdef DEBUG
			log_add(log_Warning, "bind() failed: %s.\n", strerror(errno));
#endif
		} else
			log_add(log_Error, "bind() failed: %s.\n", strerror(errno));
		Socket_close(sock);
		errno = savedErrno;
		return NULL;
	}

	listenResult = Socket_listen(sock, listenState->flags.backlog);
	if (listenResult == -1) {
		int savedErrno = errno;
		log_add(log_Error, "listen() failed: %s.\n", strerror(errno));
		Socket_close(sock);
		errno = savedErrno;
		return NULL;
	}

	nd = NetDescriptor_new(sock, (void *) listenState);
	if (nd == NULL) {
		int savedErrno = errno;
		log_add(log_Error, "NetDescriptor_new() failed: %s.\n",
				strerror(errno));
		Socket_close(sock);
		errno = savedErrno;
		return NULL;
	}
		
	NetDescriptor_setReadCallback(nd, acceptCallback);

	return nd;
}

static void
listenPortMulti(struct ListenState *listenState, struct addrinfo *info) {
	struct addrinfo *addrPtr;
	size_t addrCount;
	size_t addrOkCount;
	NetDescriptor **nds;

	// Count how many addresses we've got.
	addrCount = 0;
	for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next)
		addrCount++;

	// This is where we intend to store the file descriptors of the
	// listening sockets.
	nds = malloc(addrCount * sizeof listenState->nds[0]);

	// Bind to each address.
	addrOkCount = 0;
	for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) {
		NetDescriptor *nd;
		nd = listenPortSingle(listenState, addrPtr);
		if (nd == NULL) {
			// Failed. On to the next.
			// An error message is already printed for serious errors.
			// If the address is already in use, we here also print
			// a message when we are not already listening on one of
			// the other addresses.
			// This is because on some IPv6 capabable systems (like Linux),
			// IPv6 sockets also handle IPv4 connections, which means
			// that a separate IPv4 socket won't be able to bind to the
			// port.
			// BUG: if the IPv4 socket is in the list before the
			// IPv6 socket, it will be the IPv6 which will fail to bind,
			// so only IPv4 connections will be handled, as v4 sockets can't
			// accept v6 connections.
			// In practice, on Linux, I haven't seen it happen, but
			// it's a real possibility.
			if (errno == EADDRINUSE && addrOkCount == 0) {
				log_add(log_Error, "Error while preparing a network socket "
						"for incoming connections: %s\n", strerror(errno));
			}
			continue;
		}
		
		nds[addrOkCount] = nd;
		addrOkCount++;
	}

	freeaddrinfo(info);

	listenState->nds =
			realloc(nds, addrOkCount * sizeof listenState->nds[0]);
	listenState->numNd = addrOkCount;

	if (addrOkCount == 0) {
		// Could not listen on any port.
		ListenError error;
		error.state = Listen_listening;
		error.err = EIO;
				// Nothing better to offer.
		doListenErrorCallback(listenState, &error);
		return;
	}
}

static void
listenPortResolveCallback(ResolveState *resolveState,
		struct addrinfo *result) {
	ListenState *listenState =
			(ListenState *) ResolveState_getExtra(resolveState);
	Resolve_close(listenState->resolveState);
	listenState->resolveState = NULL;
	listenState->state = Listen_listening;
	listenPortMulti(listenState, result);
}

static void
listenPortResolveErrorCallback(ResolveState *resolveState,
		const ResolveError *resolveError) {
	ListenState *listenState =
			(ListenState *) ResolveState_getExtra(resolveState);
	ListenError listenError;

	assert(resolveError->gaiRes != 0);

	listenError.state = Listen_resolving;
	listenError.resolveError = resolveError;
	listenError.err = resolveError->err;
	doListenErrorCallback(listenState, &listenError);
}

// 'proto' is one of IPProto_tcp or IPProto_udp.
ListenState *
listenPort(const char *service, Protocol proto, const ListenFlags *flags,
		ListenConnectCallback connectCallback,
		ListenErrorCallback errorCallback, void *extra) {
	struct addrinfo	hints;
	ListenState *listenState;
	ResolveFlags resolveFlags;
			// Structure is empty (for now).

	assert(flags->familyDemand == PF_inet ||
			flags->familyDemand == PF_inet6 ||
			flags->familyDemand == PF_unspec);
	assert(flags->familyPrefer == PF_inet ||
			flags->familyPrefer == PF_inet6 ||
			flags->familyPrefer == PF_unspec);
	assert(proto == IPProto_tcp || proto == IPProto_udp);

	// Acquire a list of addresses to bind to.
	memset(&hints, '\0', sizeof hints);
	hints.ai_family = protocolFamilyTranslation[flags->familyDemand];
	hints.ai_protocol = protocolTranslation[proto];

	if (proto == IPProto_tcp) {
		hints.ai_socktype = SOCK_STREAM;
	} else {
		assert(proto == IPProto_udp);
		hints.ai_socktype = SOCK_DGRAM;
	}
	hints.ai_flags = AI_PASSIVE;

	listenState = ListenState_alloc();
	listenState->refCount = 1;
#ifdef DEBUG_LISTEN_REF
	log_add(log_Debug, "ListenState %08" PRIxPTR ": ref=1 (%d)\n",
			(uintptr_t) listenState, listenState->refCount);
#endif
	listenState->state = Listen_resolving;
	listenState->flags = *flags;
	listenState->connectCallback = connectCallback;
	listenState->errorCallback = errorCallback;
	listenState->extra = extra;
	listenState->nds = NULL;
	listenState->numNd = 0;

	listenState->resolveState = getaddrinfoAsync(NULL, service, &hints,
			&resolveFlags, listenPortResolveCallback,
			listenPortResolveErrorCallback,
			(ResolveCallbackArg) listenState);

	return listenState;
}

// NB: The callback function becomes the owner of newNd.
static void
doListenConnectCallback(ListenState *listenState, NetDescriptor *listenNd,
		NetDescriptor *newNd,
		const struct sockaddr *addr, SOCKLEN_T addrLen) {
	assert(listenState->connectCallback != NULL);

	ListenState_incRef(listenState);
	// No need to increment listenNd, as there's guaranteed to be one
	// reference from listenState. And no need to increment newNd,
	// as the callback function takes over ownership.
	(*listenState->connectCallback)(listenState, listenNd, newNd,
			addr, (socklen_t) addrLen);
	ListenState_decRef(listenState);
}

static void
doListenErrorCallback(ListenState *listenState, const ListenError *error) {
	assert(listenState->errorCallback != NULL);

	ListenState_incRef(listenState);
	(*listenState->errorCallback)(listenState, error);
	ListenState_decRef(listenState);
}

static void
acceptSingleConnection(ListenState *listenState, NetDescriptor *nd) {
	Socket *sock;
	Socket *acceptResult;
	struct sockaddr_storage addr;
	socklen_t addrLen;
	NetDescriptor *newNd;

	sock = NetDescriptor_getSocket(nd);
	addrLen = sizeof (addr);
	acceptResult = Socket_accept(sock, (struct sockaddr *) &addr, &addrLen);
	if (acceptResult == Socket_noSocket) {
		switch (errno) {
			case EWOULDBLOCK:
			case ECONNABORTED:
				// Nothing serious. Keep listening.
				return;
			case EMFILE:
			case ENFILE:
			case ENOBUFS:
			case ENOMEM:
#ifdef ENOSR
			case ENOSR:
#endif
				// Serious problems, but future connections may still
				// be possible.
				log_add(log_Warning, "accept() reported '%s'\n",
						strerror(errno));
				return;
			default:
				// Should not happen.
				log_add(log_Fatal, "Internal error: accept() reported "
						"'%s'\n", strerror(errno));
				explode();
		}
	}

	(void) Socket_setReuseAddr(acceptResult);
			// Ignore errors; it's not a big deal.
	if (Socket_setNonBlocking(acceptResult) == -1) {
		int savedErrno = errno;
		log_add(log_Error, "Could not make socket non-blocking: %s.\n",
				strerror(errno));
		Socket_close(acceptResult);
		errno = savedErrno;
		return;
	}
	(void) Socket_setInlineOOB(acceptResult);
			// Ignore errors; it's not a big deal as the other
			// party is not not supposed to send any OOB data.
	(void) Socket_setKeepAlive(sock);
			// Ignore errors; it's not a big deal.

#ifdef DEBUG
	{
		char hostname[NI_MAXHOST];
		int gniRes;

		gniRes = getnameinfo((struct sockaddr *) &addr, addrLen,
				hostname, sizeof hostname, NULL, 0, 0);
		if (gniRes != 0) {
			log_add(log_Error, "Error while performing hostname "
					"lookup for incoming connection: %s\n",
					(gniRes == EAI_SYSTEM) ? strerror(errno) :
					gai_strerror(gniRes));
		} else {
			log_add(log_Debug, "Accepted incoming connection from '%s'.\n",
					hostname);
		}
	}
#endif
	
	newNd = NetDescriptor_new(acceptResult, NULL);
	if (newNd == NULL) {
		int savedErrno = errno;
		log_add(log_Error, "NetDescriptor_new() failed: %s.\n",
				strerror(errno));
		Socket_close(acceptResult);
		errno = savedErrno;
		return;
	}

	doListenConnectCallback(listenState, nd, newNd,
			(struct sockaddr *) &addr, addrLen);
	// NB: newNd is now handed over to the callback function, and should
	//     no longer be referenced from here.
}

// Called when select() has indicated readability on a listening socket,
// i.e. when a connection is in the queue.
static void
acceptCallback(NetDescriptor *nd) {
	ListenState *listenState = (ListenState *) NetDescriptor_getExtra(nd);

	acceptSingleConnection(listenState, nd);
}


