/* uici.c  udp sockets implementation */
/* would be good to have a getmyhostinfo that returns my host and port */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include "uici.h"
#include "uiciudp.h"

/*
 *                           u_open_udp
 * Return a file descriptor.
 *  It is bound to the given port if the port is positive.
 *
 * parameter:
 *        port = number of port to bind to
 * returns:  file descriptor if successful and -1 on error
 */
int u_open_udp(u_port_t port)
{
   int sock;
   struct sockaddr_in server;
   int reuse = 1;

   if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
      return -1;

   if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
      close(sock);
      return U_ESOCKOPT;
   }
       
   if (port > 0) {
      server.sin_family = AF_INET;
      server.sin_addr.s_addr = htonl(INADDR_ANY);
      server.sin_port = htons((short)port);
 
      if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
         close(sock);
         return U_EBIND;
      }
   }
   return sock;
}

/*
 *                           u_recvfrom
 *
 * Retrieve information from a file descriptor.
 *
 * parameters:
 *       fd = socket file descriptor
 *       buf = buffer receive the data
 *       nbytes = number of bytes to retrieve
 *       ubufp buffer of size u_bufsize that will be filled in
 * returns:
 *      -1 if an error occurred
 *      otherwise the number of bytes read is returned
 */

ssize_t u_recvfrom(int fd, void *buf, size_t nbytes, u_buf_t *ubufp)
{
   int len;
   struct sockaddr *remote;
   int retval;

   len = sizeof (struct sockaddr_in);
   remote = (struct sockaddr *)ubufp;
   while ( ((retval = recvfrom(fd, buf, nbytes, 0, remote, &len)) == -1) &&
           (errno == EINTR) )
      ;  
   return retval;
}

/*
 *                           u_recvfrom_timed
 *
 * Retrieve information from a file descriptor.
 *
 * parameters:
 *       fd = socket file descriptor
 *       buf = buffer receive the data
 *       nbytes = number of bytes to retrieve
 *       ubufp buffer of size u_bufsize that will be filled in
 *       double timeout (in seconds)
 * returns:
 *      number of bytes received if no error occurred
 *      -3 if an timeout occurred
 *      -1 if an error occurred and errno is set
 * note:
 *      if interrupted by a signal, the timeout interval starts over
 */

ssize_t u_recvfrom_timed(int fd, void *buf, size_t nbytes, u_buf_t *ubufp,
                         double time)
{
   int len;
   int nfds;
   fd_set readfds;
   struct sockaddr *remote;
   int retval;
   struct timeval timeout;
   struct timeval timesave;

   nfds = fd+1;
   FD_ZERO(&readfds);
   FD_SET(fd, &readfds);
   timesave.tv_sec = (int)time;
   timesave.tv_usec = (int)((time - timesave.tv_sec)*1000000.0 + 0.5);

   timeout = timesave;
   while ( ((retval = select(nfds, &readfds, NULL, NULL, &timeout)) == -1) &&
           (errno == EINTR) ) {
      FD_SET(fd, &readfds);
      timeout = timesave;
   }
   if (retval == -1)
      return -1;

   if (retval == 0)
      return U_ETIMEOUT;
   len = sizeof (struct sockaddr_in);
   remote = (struct sockaddr *)ubufp;
   while ( ((retval = recvfrom(fd, buf, nbytes, 0, remote, &len)) == -1) &&
           (errno == EINTR) )
      ;  
   return retval;
}

/*
 *                           u_get_host_name
 *
 * Get the host name from a buffer
 *
 * parameters:
 *       ubufp buffer of size u_bufsize that was filled in by u_recvfrom
 *       hostn a buffer of size hostnsize
 *       hostsize the size of the hostn buffer
 * returns:
 *      -2 if an error occurred
 *      otherwise the name is filled in hostn, possibly truncated
 */

void u_get_host_name(u_buf_t *ubufp, char *hostn, int hostnsize)
{
   struct hostent *hostptr;
   struct sockaddr_in *remotep;

   remotep = (struct sockaddr_in *)ubufp;
   hostptr = gethostbyaddr((char *)&(remotep->sin_addr), 4, AF_INET);
   if (hostptr == NULL)
      strncpy(hostn, inet_ntoa(remotep->sin_addr), hostnsize-1);
   else
      strncpy(hostn, (*hostptr).h_name, hostnsize-1);
   hostn[hostnsize-1] = 0;
}

/*
 *                           u_get_host_info
 *
 * Get a printable string containing the host name and port
 *
 * parameters:
 *       ubufp buffer of size u_bufsize that was filled in by u_recvfrom
 *       info a buffer of size infosize
 *       infosize the size of the info buffer
 * returns:
 *      a string is filled in info, possibly truncated
 */


void u_get_host_info(u_buf_t *ubufp, char *info, int infosize)
{

   int portnumber;
   int len;

   portnumber = ntohs(ubufp->sin_port);
   len = snprintf(info,infosize,"port number is %d on host ",portnumber);
   info[infosize-1] = 0;                           /* In case it did not fit */
   if (len >= infosize) return;
   u_get_host_name(ubufp,info+len,infosize-len);
}

 
/*
 *                           u_compare_host
 *
 * Compare the given host and port with the info in the ubufp
 *
 * parameters:
 *       ubufp buffer of size u_bufsize that was filled in by u_recvfrom
 *       hostn a string representing the host name
 *       port a port number
 * returns:
 *      0 if no mathch
 *      1 if match
 */
 
int u_compare_host(u_buf_t *ubufp, char *hostn, u_port_t port)
{
   struct hostent *hostptr;
   char **p;
   struct sockaddr_in *remotep;

   remotep = (struct sockaddr_in *)ubufp;
   if (port != ntohs(remotep->sin_port))
      return 0;
   hostptr = gethostbyaddr((char *)&(remotep->sin_addr), 4, AF_INET);
   if (hostptr == NULL)
      return 0;
   if (strcmp(hostn, hostptr->h_name) == 0)
      return 1;
   for (p = hostptr->h_aliases; *p != NULL; p++)
      if (strcmp(hostn, *p) == 0)
         return 1;
   return 0;
}

 
/*
 *                           u_sendto
 *
 * Send information atomically to a remote using the buffer filled in
 * by recvfrom
 *
 * This is almost the same as sendto except that
 *   It retries if interrupted by a signal
 *   The length of the buffer indicating the destination is not passed
 *
 * parameters:
 *       fd = file descriptor
 *       buf = buffer to be output
 *       nbytes = number of bytes to send
 *       ubufp = buffer previously filled in by u_recvfrom
 * returns:
 *      -1 if a local error occurred
 *      otherwise nbyte bytes have been sent (but may never be received)
 */

ssize_t u_sendto(int fd, void *buf, size_t nbytes, u_buf_t *ubufp)
{
   int len;
   struct sockaddr *remotep;
   int retval;

   len = sizeof(struct sockaddr_in);
   remotep = (struct sockaddr *)ubufp;
   while ( ((retval = sendto(fd, buf, nbytes, 0, remotep, len)) == -1) &&
           (errno == EINTR) )
      ;  
   return retval;
}
 
/*
 *                           u_sendtohost
 *
 * Send information atomically to a remote given the host name and port
 * 
 * parameters:
 *       fd = file descriptor
 *       buf = buffer to be output
 *       nbyte = number of bytes to send
 *       port = the port number to send to
 *       hostn buffer containing the name of the destination host
 * returns:
 *      -1 if a local error occurred that sets errno
 *      -2 if the host name could not be resolved
 *      otherwise nbytes bytes have been sent (but may never be received)
 */
   
ssize_t u_sendtohost(int fd, void *buf, size_t nbytes, char *hostn,
                     u_port_t port)
{
   struct hostent *hp;
   struct sockaddr_in remote;

   if (isdigit((int)(*hostn))) {
      remote.sin_addr.s_addr = inet_addr(hostn);
   }
   else {
      if (!(hp = gethostbyname(hostn)))
         return U_EHOST;
      memcpy((char *)&remote.sin_addr, hp->h_addr_list[0], hp->h_length);
   }
   remote.sin_port = htons((short)port);
   remote.sin_family = AF_INET;

   return u_sendto(fd, buf, nbytes, &remote);
}

/*
    Joins a group for multicast
*/
int u_join(char *IP_address, u_port_t port, u_buf_t *ubufp)
{
   struct ip_mreq tempaddress;
   int mcastfd;

   if ( (mcastfd = u_open_udp(port)) == -1)
      return mcastfd;
   
   tempaddress.imr_multiaddr.s_addr = inet_addr(IP_address);
   tempaddress.imr_interface.s_addr = htonl(INADDR_ANY);

        /* Join the multicast group, Let kernel choose the interface */
   if (setsockopt(mcastfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                   &tempaddress, sizeof(tempaddress)) == -1)
      return -1;
   ubufp->sin_family = AF_INET;
   ubufp->sin_addr.s_addr = inet_addr(IP_address);
   ubufp->sin_port = htons((short)port);
   return mcastfd;
}

/* This version leaves the group be keeps the FD open and still bound to
   the same port.  It can still receive messages on the port, but only
   those addressed directly to the given host.
*/
int u_leave(int mcastfd, u_buf_t *ubufp) {  

   struct ip_mreq tempaddress;
 
   memcpy(&(tempaddress.imr_multiaddr),
         &(ubufp->sin_addr), sizeof(struct in_addr));   
   tempaddress.imr_interface.s_addr = htonl(INADDR_ANY);
   return setsockopt(mcastfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
                   &tempaddress, sizeof(tempaddress));
}




