/*!\file bind.c
 * BSD bind().
 */

/*  BSD sockets functionality for Waterloo TCP/IP
 *
 *  Copyright (c) 1997-2002 Gisle Vanem <giva@bgnett.no>
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. All advertising materials mentioning features or use of this software
 *     must display the following acknowledgement:
 *       This product includes software developed by Gisle Vanem
 *       Bergen, Norway.
 *
 *  THIS SOFTWARE IS PROVIDED BY ME (Gisle Vanem) AND CONTRIBUTORS ``AS IS''
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED.  IN NO EVENT SHALL I OR CONTRIBUTORS BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  Version
 *
 *  0.5 : Dec 18, 1997 : G. Vanem - created
 *  0.6 : Aug 06, 2002 : G. Vanem - added AF_INET6 support
 */

#include "socket.h"

#if defined(USE_BSD_API)

/*
 * Bind:
 *  The purpose of bind is to fill in the information for the local IP
 *  address and local port (local_addr). In this respect, it is the
 *  opposite of connect(). connect() fills in the destination IP address
 *  and destination port (remote_addr).
 *  In most situations, one does not need to call bind().
 *
 *  Calling bind() on a SOCK_DGRAM socket and for multiple addresses
 *  ('myaddr->sin_addr.s_addr == INADDR_ANY') requires special attention:
 *  Wattcp's 'udp_handler()' doesn't store address information for
 *  broadcast.
 *
 *  We therefore install a 's->dataHandler' pointing to '_recvdaemon()'
 *  which queues up SOCK_DGRAM messages. This queue is polled (by
 *  'sock_recv_from') when we call 'receive()' on such a socket.
 *  Thus the '*from' address in 'receive()' will be correctly set to
 *  source address/port of peer.
 */
int W32_CALL bind (int s, const struct sockaddr *myaddr, int namelen)
{
  Socket              *socket = _socklist_find (s);
  struct sockaddr_in  *addr   = (struct sockaddr_in*) myaddr;
  struct sockaddr_in6 *addr6  = (struct sockaddr_in6*) myaddr;
  BOOL   bind_any, is_bcast, is_mcast, our_addr;
  WORD   local_port;
  BOOL   is_ip6;
  int    sa_len;

  SOCK_PROLOGUE (socket, "\nbind:%d", s);

  is_ip6 = (socket->so_family == AF_INET6);
  sa_len = is_ip6 ? sizeof(*addr6) : sizeof(*addr);

  if (_sock_chk_sockaddr(socket, myaddr, namelen) < 0)
     return (-1);

  if (socket->so_type == SOCK_STREAM)
  {
    if (is_ip6)
         is_mcast = IN6_IS_ADDR_MULTICAST (&addr6->sin6_addr);
    else is_mcast = IN_MULTICAST (ntohl(addr->sin_addr.s_addr));
    if (is_mcast)
    {
      SOCK_DEBUGF ((", EINVAL (mcast)"));
      SOCK_ERRNO (EINVAL);
      return (-1);
    }
  }

  /* binding to any address/port on local machine?
   */
#if defined(USE_IPV6)
  if (is_ip6)
  {
    our_addr = _ip6_is_local_addr (&addr6->sin6_addr);
    bind_any = IN6_ARE_ADDR_EQUAL (&addr6->sin6_addr, &in6addr_any);
    is_bcast = IN6_IS_ADDR_MC_GLOBAL (&addr6->sin6_addr);   /* ?? */
  }
  else
#endif
  {
    our_addr = _ip4_is_local_addr (ntohl(addr->sin_addr.s_addr));
    bind_any = (addr->sin_addr.s_addr == INADDR_ANY);
    is_bcast = ((~addr->sin_addr.s_addr & ~sin_mask) == 0);
  }

  if (socket->so_type == SOCK_STREAM &&
      !bind_any && !our_addr && !is_bcast)
  {
    SOCK_DEBUGF ((", EADDRNOTAVAIL"));
    SOCK_ERRNO (EADDRNOTAVAIL);
    return (-1);
  }

  if (socket->local_addr)
  {
    struct in6_addr *ip6 = &((struct sockaddr_in6*) socket->local_addr)->sin6_addr;
    u_short port  = socket->local_addr->sin_port;
    u_long  ip4   = socket->local_addr->sin_addr.s_addr;
    BOOL    equal = (!is_ip6 && ip4 == addr->sin_addr.s_addr) ||
                   (is_ip6 && IN6_ARE_ADDR_EQUAL(ip6,&addr6->sin6_addr));

    if ((addr->sin_port != IPPORT_ANY && addr->sin_port == port) ||
        (!bind_any && equal))
    {
      SOCK_DEBUGF ((", EADDRINUSE, local port %d, bind_any %d",
                     ntohs(port), bind_any));
      SOCK_ERRNO (EADDRINUSE);
      return (-1);
    }

    /** \todo check for "sleeping" ports (lport_inuse in pctcp.c)
     *        also. And return EADDRINUSE if local port not free.
     */
  }
  else
  {
    socket->local_addr = (struct sockaddr_in*) SOCK_CALLOC (sa_len);
    if (!socket->local_addr)
    {
      SOCK_DEBUGF ((", ENOMEM"));
      SOCK_ERRNO (ENOMEM);
      return (-1);
    }
  }

#if defined(USE_IPV6)
  if (is_ip6 && addr6->sin6_port == IPPORT_ANY)
  {
    local_port = find_free_port (0, TRUE);
    addr6->sin6_port = htons (local_port);
  }
  else
#endif
  if (!is_ip6 && addr->sin_port == IPPORT_ANY)
  {
    local_port = find_free_port (0, TRUE);
    addr->sin_port = htons (local_port);
  }
  else  /* check if requested port is vacant */
  {
    local_port = ntohs (addr->sin_port);
    if (grab_localport(local_port) < 0)
    {
      SOCK_DEBUGF ((", EADDRNOTAVAIL, local port %d", local_port));
      SOCK_ERRNO (EADDRNOTAVAIL);
      return (-1);
    }
  }

#if defined(USE_IPV6)
  if (is_ip6)
  {
    struct sockaddr_in6 *la = (struct sockaddr_in6*)socket->local_addr;

    la->sin6_family = AF_INET6;
    la->sin6_port   = addr6->sin6_port;
    memcpy (&la->sin6_addr, &addr6->sin6_addr, sizeof(la->sin6_addr));

    SOCK_DEBUGF ((", %s, (%d)",
                  bind_any ? "INADDR_ANY" : _inet6_ntoa(&addr6->sin6_addr),
                  ntohs(addr->sin_port)));
  }
  else
#endif
  {
    socket->local_addr->sin_port   = addr->sin_port;
    socket->local_addr->sin_addr   = addr->sin_addr;
    socket->local_addr->sin_family = AF_INET;

    SOCK_DEBUGF ((", %s, (%d)",
                  bind_any ? "INADDR_ANY" : inet_ntoa(addr->sin_addr),
                  ntohs(addr->sin_port)));
  }

  /* Since SOCK_DGRAM sockets are connectionless, the application need
   * not use connect() or accept(). Hence we need to use _UDP_listen().
   */
  if (socket->so_type == SOCK_DGRAM)
  {
    if (bind_any)
       socket->so_state |= SS_PRIV; /* privileged for broadcast reception */

#if defined(USE_IPV6)
    if (is_ip6)
    {
      if (_UDP6_listen (socket, &addr6->sin6_addr, addr6->sin6_port) < 0)
         return (-1);
    }
    else
#endif
    if (_UDP_listen (socket, addr->sin_addr, addr->sin_port) < 0)
       return (-1);
  }
  return (0);
}


/*
 * A small test djgpp program
 */
#if defined(TEST_PROG)

#define MY_PORT_ID  6060
#undef  close

#include <unistd.h>
#include <conio.h>
#include "pcdbug.h"

int main (int argc, char **argv)
{
  struct sockaddr_in addr;
  int    sock, quit;

  dbug_init();

  sock = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (sock < 0)
  {
    perror ("socket");
    return (-1);
  }

  memset (&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;

  if (argc >= 2 && !strcmp(argv[1],"-frag")) /* test Tx of large datagrams */
  {
    #define CHUNK_SIZE 500
    char data [3*CHUNK_SIZE];
    int  i;

    for (i = 0; i < sizeof(data);  i++)
        data[i] = i;

    addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    addr.sin_port        = IPPORT_ANY;

    if (sendto (sock, &data, sizeof(data), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0)
       perror ("sendto");

    close (sock);
    return (-1);
  }

  /* INADDR_ANY will take all the address of the system
   */
  addr.sin_addr.s_addr = htonl (INADDR_ANY);
  addr.sin_port        = htons (MY_PORT_ID);

  if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
  {
    perror ("bind");
    close (sock);
    return (-1);
  }

#if 0
  if (listen (sock, 5) < 0)
  {
    perror ("listen");
    close (sock);
    return (-1);
  }
#endif

  quit = 0;

  while (!quit)
  {
    struct timeval  tv;
    struct sockaddr from;
    fd_set fd_read, fd_write, fd_exc;
    int    num, from_len = sizeof(from);

    FD_ZERO (&fd_read);
    FD_ZERO (&fd_write);
    FD_ZERO (&fd_exc);
    FD_SET (STDIN_FILENO, &fd_read);
    FD_SET (sock, &fd_read);
    FD_SET (sock, &fd_write);
    FD_SET (sock, &fd_exc);
    tv.tv_usec = 0;
    tv.tv_sec  = 1;

    num = select (sock+1, &fd_read, &fd_write, &fd_exc, &tv);

    if (FD_ISSET(sock, &fd_read))  fputc ('r', stderr);
    if (FD_ISSET(sock, &fd_write)) fputc ('w', stderr);
    if (FD_ISSET(sock, &fd_exc))   fputc ('x', stderr);

    if (FD_ISSET(STDIN_FILENO, &fd_read))
    {
      int ch = getch();
      quit = (ch == 27);
      fputc (ch, stderr);
    }

    if (FD_ISSET(sock, &fd_read) &&
        accept (sock, &from, &from_len) < 0)
    {
      perror ("accept");
      break;
    }
    if (num < 0)
    {
      perror ("select");
      break;
    }
    fputc ('.', stderr);
    usleep (300000);   /* 300ms */
  }

  close (sock);
  return (0);
}
#endif  /* TEST_PROG */

#endif /* USE_BSD_API */

