/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

int readudp(void *p,void *buf,u_int len)
{
	udpinfo *info = (udpinfo*)p;
	marssocklen_t sinlen = sizeof(struct sockaddr_in);
	int done = 0;

	while(!done)
	{
		fd_set fds;
		struct timeval *t = &info->timeout;

		if(t->tv_sec == 0 && t->tv_usec == 0)
			t = NULL;

		FD_ZERO(&fds); 
		FD_SET(info->soc,&fds);

		switch(select(FD_SETSIZE,&fds,NULL,NULL,t))
		{
			case 0:
				/* marslog(LOG_EROR,"readudp timeout"); */
				/* time out */
				return -1;
				/* break; */

			case -1:
				if(errno != EINTR)
				{
					marslog(LOG_EROR|LOG_PERR,"select");
					return -1;
				}
				break;

			default:
				done = 1;
				break;
		}
	}

	len = recvfrom(info->soc,buf,len,0,(struct sockaddr*)&info->sin,&sinlen);
	/* print_address("recvfrom",&info->sin); */
	return len;
}

int writeudp(void *p,void *buf,u_int len)
{
	udpinfo *info = (udpinfo*)p;
	int n = sendto(info->soc,buf,len,0,(struct sockaddr*)&info->sin,
		sizeof(struct sockaddr_in));
	/* print_address("sendto",&info->sin); */
	if(n != len)
		marslog(LOG_EROR|LOG_PERR,"sendto failed");
	return n;
}

int udp_socket(int port)
{

	int flg;
	int s;

#ifdef SO_LINGER
	struct  linger ling;
#endif
	struct sockaddr_in sin;

	s = socket(AF_INET, SOCK_DGRAM, 0);

	if (s < 0)
	{
		marslog(LOG_EROR|LOG_PERR,"socket");
		return -1;
	}

	flg = 1 ;
	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (msockopt)&flg, sizeof(flg))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_REUSEADDR");

/*
	flg = 1 ;
	if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (msockopt)&flg, sizeof(flg))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_KEEPALIVE");
*/

#ifdef SO_REUSEPORT
	flg = 1 ;
	if(setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (msockopt)&flg, sizeof(flg))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_REUSEPORT");
#endif

#ifdef SO_LINGER
	ling.l_onoff = 0;
	ling.l_linger = 0;
	if(setsockopt(s, SOL_SOCKET, SO_LINGER, (msockopt)&ling, sizeof(ling))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_LINGER");
#else
#ifdef SO_DONTLINGER
	if(setsockopt(s, SOL_SOCKET, SO_DONTLINGER, NULL, 0)<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_DONTLINGER");
#endif
#endif


	bzero(&sin, sizeof(struct sockaddr_in));
	sin.sin_port        = htons(port);
	sin.sin_family      = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;

	while (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1)
	{
		marslog(LOG_WARN|LOG_PERR,"bind port = %d",port);
		sleep(5);
	}

	return s ;
}

udpinfo *udp_server(int port)
{
	udpinfo *info = NEW(udpinfo);
	info->soc = udp_socket(port);
	if(info->soc < 0) {
		FREE(info);
		return NULL;
	}
	bzero(&info->sin,sizeof(struct sockaddr_in ));
	info->timeout.tv_sec  = 0;
	info->timeout.tv_usec = 0;
	return info;
}

udpinfo *udp_call(const char *host, int port)
{
#ifdef INADDR_NONE

	in_addr_t addr;
	in_addr_t none = INADDR_NONE;

#else

#ifdef __alpha
	unsigned int  addr;
    unsigned int  none = (unsigned int)~0;
#elif defined(fujitsu)
	u_int addr;
    u_int none = (u_int)~0;
#else
	unsigned long addr;
    unsigned long none = (unsigned long)-1;
#endif
#endif
	struct hostent *him;
	udpinfo *info = NEW(udpinfo);

	/* create a socket first */	

	info->soc = udp_socket(0);
	if(info->soc < 0) {
		FREE(info);
		return NULL;
	}

	bzero(&info->sin,sizeof(struct sockaddr_in ));

	info->sin.sin_port = htons(port);
	info->sin.sin_family = AF_INET;

	marslog(LOG_DBUG,"Calling \"%s\" port %d",host,port);

	addr = inet_addr(host);
	info->sin.sin_addr.s_addr = addr;

	if(addr == none) {
		if ((him=gethostbyname(host))==NULL)
		{
			marslog(LOG_EROR,"unknown host : %s",host);
			close(info->soc);
			FREE(info);
			return NULL;
		}
		info->sin.sin_family = him->h_addrtype;
		bcopy(him->h_addr_list[0],&info->sin.sin_addr,him->h_length);
	}

	info->timeout.tv_sec  = 2;
	info->timeout.tv_usec = 0; /* 1/2 sec */

	return info;
}

void udp_free(udpinfo *info)
{
	close(info->soc);
	FREE(info);
}
