#include "stdafx.h"
#include "udp.h"

static bool host_port(char *buf, char **host, char **port)
{
	const char *ptr = acl_host_port(buf, host, "", port, (char*) NULL);

	if (ptr != NULL) {
		acl_msg_error("%s(%d): invalid addr %s, %s",
			__FILE__, __LINE__, buf, ptr);
		return false;
	}

	if (*port == NULL || atoi(*port) < 0) {
		acl_msg_error("%s(%d): invalid port: %s, addr: %s",
			__FILE__, __LINE__, *port ? *port : "null", buf);
		return false;
	}

	if (*host && **host == 0)
		*host = 0;
	if (*host == NULL)
		*host = "0";

	return true;
}

static struct addrinfo *host_addrinfo(const char *addr)
{
	int    err;
	struct addrinfo hints, *res0;
	char  *buf = acl_mystrdup(addr), *host = NULL, *port = NULL;

	if (host_port(buf, &host, &port) == false) {
		acl_myfree(buf);
		return NULL;
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family   = PF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
#ifdef  ACL_MACOSX
	hints.ai_flags    = AI_DEFAULT;
#elif   defined(ACL_ANDROID)
	hints.ai_flags    = AI_ADDRCONFIG;
#elif defined(ACL_WINDOWS)
	hints.ai_protocol = IPPROTO_UDP;
# if _MSC_VER >= 1500
	hints.ai_flags    = AI_V4MAPPED | AI_ADDRCONFIG;
# endif
#else
	hints.ai_flags    = AI_V4MAPPED | AI_ADDRCONFIG;
#endif
	if ((err = getaddrinfo(host, port, &hints, &res0))) {
		acl_msg_error("%s(%d): getaddrinfo error %s, peer=%s",
			__FILE__, __LINE__, gai_strerror(err), host);
		acl_myfree(buf);
		return NULL;
	}

	acl_myfree(buf);
	return res0;
}

static int bind_addr(struct addrinfo *res0, struct addrinfo **res)
{
	struct addrinfo *it;
	int   on, fd;

	for (it = res0; it != NULL ; it = it->ai_next) {
		fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
		if (fd == ACL_SOCKET_INVALID) {
			acl_msg_error("%s(%d): create socket %s",
				__FILE__, __LINE__, acl_last_serror());
			return ACL_SOCKET_INVALID;
		}

		on = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
			(const void *) &on, sizeof(on)) < 0)
		{
			acl_msg_warn("%s(%d): setsockopt(SO_REUSEADDR): %s",
				__FILE__, __LINE__, acl_last_serror());
		}

#if defined(SO_REUSEPORT)
		on = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
			(const void *) &on, sizeof(on)) < 0)
		{
			acl_msg_warn("%s(%d): setsocket(SO_REUSEPORT): %s",
				__FILE__, __LINE__, acl_last_serror());
		}
#endif

#ifdef ACL_WINDOWS
		if (bind(fd, it->ai_addr, (int) it->ai_addrlen) == 0)
#else
		if (bind(fd, it->ai_addr, it->ai_addrlen) == 0)
#endif
		{
			*res = it;
			return fd;
		}

		acl_msg_error("%s(%d): bind error %s",
			__FILE__, __LINE__, acl_last_serror());
		acl_socket_close(fd);
	}

	return ACL_SOCKET_INVALID;
}

static SOCK_UDP *sock_open(const char *addr)
{
	SOCK_UDP *sock;
	struct addrinfo *res0, *res;
	int    fd;

	res0 = host_addrinfo(addr);
	if (res0 == NULL)
		return NULL;

	fd = bind_addr(res0, &res);
	if (fd == ACL_SOCKET_INVALID) {
		acl_msg_error("%s(%d): invalid socket", __FILE__, __LINE__);
		freeaddrinfo(res0);
		return NULL;
	}

	sock = (SOCK_UDP *) acl_mycalloc(1, sizeof(SOCK_UDP));
	sock->fd = fd;
	memcpy(&sock->sa_local, res->ai_addr, res->ai_addrlen);
	sock->sa_local_len = res->ai_addrlen;

	freeaddrinfo(res0);
	return sock;
}

SOCK_UDP *udp_client_open(const char *local, const char *peer)
{
	struct addrinfo *peer_res0 = host_addrinfo(peer);
	SOCK_UDP *sock;

	if (peer_res0 == NULL)
		return NULL;

	sock = sock_open(local);
	if (sock == NULL) {
		freeaddrinfo(peer_res0);
		return NULL;
	}

	memcpy(&sock->sa_peer, peer_res0->ai_addr, peer_res0->ai_addrlen);
	sock->sa_peer_len = peer_res0->ai_addrlen;
	freeaddrinfo(peer_res0);
	return sock;
}

SOCK_UDP *udp_server_open(const char *local)
{
	return sock_open(local);
}

void udp_close(SOCK_UDP *sock)
{
	acl_socket_close(sock->fd);
	acl_myfree(sock);
}

int udp_read(SOCK_UDP *sock, void *buf, size_t size)
{
	ssize_t ret;

	sock->sa_peer_len = sizeof(sock->sa_peer);
	ret = recvfrom(sock->fd, buf, size, 0,
		(struct sockaddr *) &sock->sa_peer, &sock->sa_peer_len);
	return ret;
}

int udp_send(SOCK_UDP *sock, const void *data, size_t len)
{
	ssize_t ret = sendto(sock->fd, data, len, 0,
		(struct sockaddr *) &sock->sa_peer, sock->sa_peer_len);
	return ret;
}

void udp_pkt_set_buf(SOCK_PKT *pkt, char *buf, size_t len)
{
	pkt->iov.iov_base = buf;
	pkt->iov.iov_len  = len;
	pkt->addr_len     = (socklen_t) sizeof(SOCK_ADDR);
}

int udp_pkt_set_peer(SOCK_PKT *pkt, const char *addr)
{
	struct addrinfo *peer_res0 = host_addrinfo(addr);

	if (peer_res0 == NULL)
		return -1;
	memcpy(&pkt->addr, peer_res0->ai_addr, peer_res0->ai_addrlen);
	pkt->addr_len = peer_res0->ai_addrlen;
	freeaddrinfo(peer_res0);
	return 0;
}

int udp_mread(SOCK_UDP *sock, SOCK_PKT pkts[], size_t pkts_cnt)
{
	unsigned int flags = MSG_WAITFORONE /* | MSG_DONTWAIT */;
	size_t i;

	if (sock->msgvec == NULL) {
		sock->vlen   = pkts_cnt;
		sock->msgvec = (struct mmsghdr *)
			acl_mycalloc(pkts_cnt, sizeof(struct mmsghdr));
	} else if (sock->vlen < pkts_cnt) {
		acl_myfree(sock->msgvec);
		sock->vlen   = pkts_cnt;
		sock->msgvec = (struct mmsghdr *)
			acl_mycalloc(pkts_cnt, sizeof(struct mmsghdr));
	}

	sock->pkts     = pkts;
	sock->pkts_cnt = pkts_cnt;

	memset(sock->msgvec, 0, sizeof(struct mmsghdr) * pkts_cnt);

	for (i = 0; i < pkts_cnt; i++) {
		sock->msgvec[i].msg_hdr.msg_iov     = &pkts[i].iov;
		sock->msgvec[i].msg_hdr.msg_iovlen  = 1;
		sock->msgvec[i].msg_hdr.msg_name    = &pkts[i].addr;
		sock->msgvec[i].msg_hdr.msg_namelen = sizeof(pkts[i].addr);
		sock->msgvec[i].msg_len             = 0;
	}

	return recvmmsg(sock->fd, sock->msgvec, pkts_cnt, flags, NULL);
}

int udp_msend(SOCK_UDP *sock, SOCK_PKT pkts[], size_t pkts_cnt)
{
	size_t i;
	unsigned int flags = 0;
#ifndef __linux3__
	int   n = 0;
#endif

	if (sock->msgvec == NULL) {
		sock->vlen   = pkts_cnt;
		sock->msgvec = (struct mmsghdr *)
			acl_mycalloc(pkts_cnt, sizeof(struct mmsghdr));
	} else if (sock->vlen < pkts_cnt) {
		acl_myfree(sock->msgvec);
		sock->vlen   = pkts_cnt;
		sock->msgvec = (struct mmsghdr *)
			acl_mycalloc(pkts_cnt, sizeof(struct mmsghdr));
	}

	sock->pkts     = pkts;
	sock->pkts_cnt = pkts_cnt;

	memset(sock->msgvec, 0, sizeof(struct mmsghdr) * pkts_cnt);

	for (i = 0; i < pkts_cnt; i++) {
		sock->msgvec[i].msg_hdr.msg_iov     = &pkts[i].iov;
		sock->msgvec[i].msg_hdr.msg_iovlen  = 1;
		sock->msgvec[i].msg_hdr.msg_name    = &pkts[i].addr;
		sock->msgvec[i].msg_hdr.msg_namelen = sizeof(pkts[i].addr);
		sock->msgvec[i].msg_len             = 0;
#ifndef __linux3__
		if (sendmsg(sock->fd, &sock->msgvec[i].msg_hdr, flags) < 0)
			return -1;
		n++;
#endif
	}

#ifdef __linux3__
	return sendmmsg(sock->fd, sock->msgvec, pkts_cnt, flags);
#else
	return n;
#endif
}

int pkt_port(SOCK_PKT *pkt)
{
	if (pkt->addr.sa.sa.sa_family == AF_INET) {
		struct sockaddr_in *in = &pkt->addr.sa.in;
		return ntohs(in->sin_port);
	}
#ifdef AF_INET6
	else if (pkt->addr.sa.sa.sa_family == AF_INET6) {
		struct sockaddr_in6 *in = &pkt->addr.sa.in6;
		return ntohl(in->sin6_port);
	}
#endif
	else {
		acl_msg_error("%s(%d): unkown sa_family=%d",
			__FUNCTION__, __LINE__, pkt->addr.sa.sa.sa_family);
		return -1;
	}
}

int udp_port(SOCK_UDP *sock, size_t i)
{
	if (sock->msgvec == NULL) {
		acl_msg_error("%s(%d): msgvec NULL", __FUNCTION__, __LINE__);
		return -1;
	}
	if (sock->pkts == NULL) {
		acl_msg_error("%s(%d): pkts NULL", __FUNCTION__, __LINE__);
		return -1;
	}
	if (sock->pkts_cnt == 0) {
		acl_msg_error("%s(%d): pkts_cnt 0", __FUNCTION__, __LINE__);
		return -1;
	}
	if (i >= sock->pkts_cnt) {
		acl_msg_error("%s(%d): invalid i=%d >= %d",
			__FUNCTION__, __LINE__, (int) i, (int) sock->pkts_cnt);
		return -1;
	}

	return pkt_port(&sock->pkts[i]);
}

const char *pkt_ip(SOCK_PKT *pkt, char *buf, size_t size)
{
	if (pkt->addr.sa.sa.sa_family == AF_INET) {
		struct sockaddr_in *in = &pkt->addr.sa.in;
		return inet_ntop(in->sin_family, &in->sin_addr, buf, size);
	}
#ifdef AF_INET6
	else if (pkt->addr.sa.sa.sa_family == AF_INET6) {
		struct sockaddr_in6 *in = &pkt->addr.sa.in6;
		return inet_ntop(in->sin6_family, &in->sin6_addr, buf, size);
	}
#endif
	else {
		acl_msg_error("%s(%d): unkown sa_family=%d",
			__FUNCTION__, __LINE__, pkt->addr.sa.sa.sa_family);
		return NULL;
	}
}

const char *udp_ip(SOCK_UDP *sock, size_t i, char *buf, size_t size)
{
	if (sock->msgvec == NULL) {
		acl_msg_error("%s(%d): msgvec NULL", __FUNCTION__, __LINE__);
		return NULL;
	}
	if (sock->pkts == NULL) {
		acl_msg_error("%s(%d): pkts NULL", __FUNCTION__, __LINE__);
		return NULL;
	}
	if (sock->pkts_cnt == 0) {
		acl_msg_error("%s(%d): pkts_cnt 0", __FUNCTION__, __LINE__);
		return NULL;
	}
	if (i >= sock->pkts_cnt) {
		acl_msg_error("%s(%d): invalid i=%d >= %d",
			__FUNCTION__, __LINE__, (int) i, (int) sock->pkts_cnt);
		return NULL;
	}

	return pkt_ip(&sock->pkts[i], buf, size);
}

