/* ========================================================================== */
/*! \file
 * \brief This is a wrapper to map BSD functionality of OS into POSIX subsystem
 *
 * Copyright (c) 2012-2021 by the developers. See the LICENSE file for details.
 *
 * BSD functions not defined in POSIX.1-1990 or XSI extension should be called
 * from here.
 *
 * Why do it this way:
 * The POSIX subsystem defines '_POSIX_C_SOURCE' because otherwise some systems
 * by default expose functions that are not POSIX conformant (for preserving
 * compatibility to historical versions). Indeed in many cases they have
 * conformant versions too, and with the definition they get exposed instead of
 * the default ones so we can use them. That is in general the desired
 * behaviour. But as a side effect we can't access all non-POSIX functions any
 * more. We have asked for POSIX and we got POSIX. This wrapper pulls in the
 * historical BSD stuff that can't be avoided. And because using it this way is
 * a pain in the ass, the programmer is forced to use it only if POSIX offers no
 * other option.
 */


/* ========================================================================== */
/* Configuration (must be 1st) */

#include "config.h"


/* ========================================================================== */
/* The socket API is included in POSIX.1g, POSIX.1-2001 and XSI extension */

#if CFG_USE_POSIX_API < 200112 && !CFG_USE_XSI && !CFG_USE_IP6


/* ========================================================================== */
/* BSD declarations (must be 2nd) */

#define BSD_FOR_POSIX
#include "bsd.h"


/* ========================================================================== */
/* Include headers */

#include <limits.h>

#include "main.h"


/* ========================================================================== */
/* Constants */

/* Message prefix for POSIX module */
#define MAIN_ERR_PREFIX  "POSIX: "


/* ========================================================================== */
/* ARPA internet stuff */

#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>

#if CFG_HDR_INTTYPES_H
uint16_t  posix_htons(uint16_t  host16)
#else
unsigned short int  posix_htons(unsigned short int  host16)
#endif  /* CFG_HDR_INTTYPES_H */
{
   return(htons(host16));
}

#if CFG_HDR_INTTYPES_H
uint32_t  posix_htonl(uint32_t  host32)
#else
unsigned long int  posix_htonl(unsigned long int  host32)
#endif  /* CFG_HDR_INTTYPES_H */
{
   return(htonl(host32));
}

#if CFG_HDR_INTTYPES_H
uint16_t  posix_ntohs(uint16_t  net16)
#else
unsigned short int  posix_ntohs(unsigned short int  net16)
#endif  /* CFG_HDR_INTTYPES_H */
{
   return(ntohs(net16));
}

#if CFG_HDR_INTTYPES_H
uint32_t  posix_ntohl(uint32_t  net32)
#else
unsigned long int  posix_ntohl(unsigned long int  net32)
#endif  /* CFG_HDR_INTTYPES_H */
{
   return(ntohl(net32));
}


/* This implementation is incomplete, but sufficient for 'posix_inet_pton()' */
posix_in_addr_t  posix_inet_addr(const char*  cp)
{
   posix_in_addr_t  res = (posix_in_addr_t) -1;
   unsigned long int  a, b, c, d;
   int  rv;

   if(NULL != cp)
   {
      rv = sscanf(cp, "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
      if(4 == rv)
      {
         if(256L > a && 256L > b && 256L > c && 256L > d)
         {
            res = (posix_in_addr_t) posix_htonl(a << 24 | b << 16 | c << 8 | d);
         }
      }
   }

   return(res);
}


struct_posix_servent*  posix_getservbyname(const char*  name,
                                           const char*  proto)
{
   static struct_posix_servent  se;
   struct servent*  tmp;

   tmp = getservbyname(name, proto);
   if(NULL == tmp)  { return(NULL); }
   else
   {
      se.s_name = tmp->s_name;
      se.s_aliases = tmp->s_aliases;
      se.s_port = tmp->s_port;
      se.s_proto = tmp->s_proto;
   }

   return(&se);
}


struct_posix_hostent*  posix_gethostbyname(const char*  name)
{
   static struct_posix_hostent  he;
   struct hostent*  tmp;

   tmp = gethostbyname(name);
   if(NULL == tmp)  { return(NULL); }
   else
   {
      he.h_name = tmp->h_name;
      he.h_aliases = tmp->h_aliases;
      he.h_addrtype = POSIX_AF_INET;
      he.h_length = tmp->h_length;
      he.h_addr_list = tmp->h_addr_list;
   }

   return(&he);
}


/* ========================================================================== */
/* Create socket */

#include <sys/socket.h>

int  posix_socket(int  domain, int  type, int  protocol)
{
   switch(domain)
   {
      case POSIX_AF_INET:
      {
         domain = AF_INET;
         break;
      }
      default:
      {
         errno = EAFNOSUPPORT;
         return(-1);
      }
   }

   switch(type)
   {
      case POSIX_SOCK_STREAM:
      {
         type = SOCK_STREAM;
         break;
      }
      default:
      {
         errno = EPROTOTYPE;
         return(-1);
      }
   }

   return(socket(domain, type, protocol));
}


/* ========================================================================== */
/* Connect socket */

#include <sys/socket.h>

int  posix_connect(int  sd, const struct_posix_sockaddr*  address,
                   posix_socklen_t  address_len)
{
   struct sockaddr_in  sai;
   struct sockaddr_in*  sap = &sai;

   switch(address->sa_family)
   {
      case POSIX_AF_INET:
      {
         sap->sin_family = AF_INET;
         memcpy(&sap->sin_port,
                &((struct_posix_sockaddr_in*) address)->sin_port, 2);
         memcpy(&sap->sin_addr,
                &((struct_posix_sockaddr_in*) address)->sin_addr, 4);
         break;
      }
      default:
      {
         errno = EAFNOSUPPORT;
         return(-1);
      }
   }

   return(connect(sd, (struct sockaddr*) sap,
                  (socklen_t) sizeof(struct sockaddr)));
}


/* ========================================================================== */
/* Get socket options */

#include <sys/socket.h>

int  posix_getsockopt(int  sd, int  level, int  optname,
                      void*  optval, posix_socklen_t*  optlen)
{
   int  res;
#if defined(_AIX) || defined(__hpux)
   size_t  bsd_optlen = (size_t) *optlen;
#else  /* defined(_AIX) || defined(__hpux) */
   /* This is the genuine BSD socket API */
   int  bsd_optlen = (int) *optlen;
#endif  /* defined(_AIX) || defined(__hpux) */

   /* Check buffer size */
   if((posix_socklen_t) INT_MAX < *optlen || 0 > bsd_optlen)
   {
      PRINT_ERROR("Invalid parameter value for optlen in getsockopt()");
      return(-1);
   }

   switch(level)
   {
      case POSIX_SOL_SOCKET:
      {
         level = SOL_SOCKET;
         break;
      }
      default:
      {
         errno = EINVAL;
         return(-1);
      }
   }

   switch(optname)
   {
      case POSIX_SO_ERROR:
      {
         optname = SO_ERROR;
         break;
      }
      default:
      {
         errno = ENOPROTOOPT;
         return(-1);
      }
   }

   /*
    * The original BSD API used a pointer of type 'int' for parameter 'optlen'
    * The initial value must be positive and the system never return a larger
    * value. This limits the required range to 0..INT_MAX as checked above.
    *
    * It looks like some implementations (even more recent real BSDs) today
    * intentionally typedef 'socklen_t' to 'unsigned int' because this doesn't
    * break the BSD ABI and is backward compatible for the required value range
    * (at least on all systems that use 2's complement arithmetic).
    * Therefore check again carefully your warning/error messages
    * => If you get a warning about wrong signedness here, ignore it
    * => If you get a warning about incompatible pointer type:
    *    Most likely your system does support the POSIX 'socklen_t' type and
    *    provides no BSD compatibility. Try to enable forced usage of XSI
    *    extension in the configuration file. If it doesn't work this way,
    *    send a bug report and an OS specific workaround must be implemented.
    */
   res = getsockopt(sd, level, optname, optval, &bsd_optlen);
   if(!res)  { *optlen = (posix_socklen_t) bsd_optlen; }

   return(res);
}


/* ========================================================================== */
/* Set socket options */

#include <sys/socket.h>

int  posix_setsockopt(int  sd, int  level, int  optname,
                      const void*  optval, posix_socklen_t  optlen)
{
   switch(level)
   {
      case POSIX_SOL_SOCKET:
      {
         level = SOL_SOCKET;
         break;
      }
      default:
      {
         errno = EINVAL;
         return(-1);
      }
   }

   errno = ENOPROTOOPT;
   return(-1);
}


/* ========================================================================== */
/* Synchronous I/O multiplexing */

#if CFG_HDR_SYS_SELECT_H
#  include <sys/select.h>
#endif  /* CFG_HDR_SYS_SELECT_H */
#include <sys/time.h>
#include <limits.h>

#include <stdio.h>
#include <stdlib.h>
int  posix_poll(struct_posix_pollfd  fds[], posix_nfds_t  nfds, int  timeout)
{
   int  res;
   posix_nfds_t  i;
   int  fdmax = 0;
   fd_set  readfds;
   fd_set  writefds;
   fd_set  errorfds;
   struct timeval  to;
   struct timeval*  top = &to;

   /* Prepare FD structures */
   FD_ZERO(&readfds);
   FD_ZERO(&writefds);
   FD_ZERO(&errorfds);
   for(i = 0; i < nfds; ++i)
   {
      if(fdmax < fds[i].fd)  { fdmax = fds[i].fd; }
      fds[i].revents = 0;
      if (0 < fds[i].fd)
      {
         /* Set bit for read */
         if (POSIX_POLLIN & fds[i].events)
         {
            FD_SET(fds[i].fd, &readfds);
         }
         /* Set bit for write */
         if (POSIX_POLLOUT & fds[i].events)
         {
            FD_SET(fds[i].fd, &writefds);
         }
         /* Set bit for write */
         FD_SET(fds[i].fd, &errorfds);
      }
   }

   /* Convert timeout */
   if(0 < timeout)
   {
      to.tv_sec = (timeout / 1000);
      to.tv_usec = (long int) (timeout % 1000) * (long int) 1000;
   }
   else
   {
      to.tv_sec = 0;
      to.tv_usec = 0;
   }
   if(0 > timeout)  { top = NULL; }

   /* Check fd range */
   if (INT_MAX == fdmax)
   {
      PRINT_ERROR("Number of fd too large in poll()");
      errno = EINVAL;
      res = -1;
   }
   else
   {
      /* Use 'select()' to emulate 'poll()' */
      res = select(++fdmax, &readfds, &writefds, NULL, top);
   }

   /* Prepare results */
   if (-1 == res)
   {
      /* Error */
      switch(errno)
      {
         case EINTR:
         {
            /* Interrupted by signal is valid for 'poll()' too */
            break;
         }
         default:
         {
            /* Map other errors to EINVAL */
            errno = EINVAL;
            break;
         }
      }
   }
   if (0 < res)
   {
      for(i = 0; i < nfds; ++i)
      {
         if (FD_ISSET(fds[i].fd, &readfds))
         {
            fds[i].revents |= POSIX_POLLIN;
         }
         if (FD_ISSET(fds[i].fd, &writefds))
         {
            fds[i].revents |= POSIX_POLLOUT;
         }
         if (FD_ISSET(fds[i].fd, &errorfds))
         {
            /* Set all error bits because we do not know which one it is */
            fds[i].revents |= POSIX_POLLERR | POSIX_POLLHUP | POSIX_POLLNVAL;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/* Socket I/O */

#include <sys/socket.h>
#include <sys/types.h>

ssize_t  posix_send(int  sd, const void*  buf, size_t  len, int  flags)
{
   return(send(sd, buf, len, flags));
}

ssize_t  posix_recv(int  sd, void*  buf, size_t  len, int  flags)
{
   return(recv(sd, buf, len, flags));
}


#else  /* CFG_USE_POSIX_API < 200112 && !CFG_USE_XSI && !CFG_USE_IP6 */


/* Dummy variable to avoid warnings like "empty translation unit" */
extern int  posix_bsd_dummy;
int  posix_bsd_dummy = 0;


#endif  /* CFG_USE_POSIX_API < 200112 && !CFG_USE_XSI && !CFG_USE_IP6 */


/* EOF */
