/*
 * Copyright (c) 2015, EURECOM (www.eurecom.fr)
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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.
 *
 * The views and conclusions contained in the software and documentation are those
 * of the authors and should not be interpreted as representing official policies,
 * either expressed or implied, of the FreeBSD Project.
 */
/*****************************************************************************
 Source   socket.c

 Version    0.1

 Date   2012/02/28

 Product    NAS stack

 Subsystem  Utilities

 Author   Frederic Maurel

 Description  Implements socket handlers

 *****************************************************************************/

#include "socket.h"
#include "commonDef.h"

#include <stdlib.h> // malloc, free, atoi
#include <string.h> // memset
#include <unistd.h> // close
#include <errno.h>  // EINTR
#include <sys/types.h>
#include <sys/socket.h> // socket, setsockopt, connect, bind, recv, send
#include <netdb.h>  // getaddrinfo
/****************************************************************************/
/****************  E X T E R N A L    D E F I N I T I O N S  ****************/
/****************************************************************************/

/****************************************************************************/
/*******************  L O C A L    D E F I N I T I O N S  *******************/
/****************************************************************************/

/* -------------------------------------
 * Identifier of network socket endpoint
 * -------------------------------------
 *  A network socket endpoint is defined with a type (client or server),
 *  a port number, and the name and address of the remote host it should
 *  be connected to. A socket file descriptor is created to handle the
 *  setup of the communication channel with the remote peer.
 */
struct socket_id_s {
  int type; /* connection type (client/server)  */
  int port; /* port number      */
#define SOCKET_HOSTNAME_SIZE  32
  char rhost[SOCKET_HOSTNAME_SIZE]; /* remote hostname    */
  struct sockaddr_storage addr; /* remote address   */
  int fd; /* socket file descriptor */
};

/* Set socket option at the sockets API level (SOL_SOCKET) */
static int _socket_set_option(int sfd);

/****************************************************************************/
/******************  E X P O R T E D    F U N C T I O N S  ******************/
/****************************************************************************/

/****************************************************************************
 **                                                                        **
 ** Name:  socket_udp_open()                                         **
 **                                                                        **
 ** Description: Initializes a communication endpoint for any IPv4 and     **
 **    IPv6 protocol used by a local host to send/receive UDP    **
 **    datagrams to/from a remote host in "connected" mode of    **
 **    operation.                                                **
 **                                                                        **
 ** Inputs:  type:    Type of the connection (client/server)     **
 **      host:    For client application, the remote host-   **
 **       name, or numerical IPv4 or IPv6 network    **
 **       address to connect to. NULL for server     **
 **       application.                               **
 **      port:    The port number                            **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  A pointer to the local endpoint identifier **
 **       that has been allocated for communication  **
 **       with the remote peer. NULL if the setup of **
 **       the communication endpoint failed.         **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
void* socket_udp_open(int type, const char* host, const char* port)
{
  struct addrinfo socket_info; /* endpoint information    */
  struct addrinfo *socket_addr, *sp; /* endpoint address    */
  int sfd; /* socket file descriptor  */

  /*
   * Parameters sanity check
   * -----------------------
   * The local connection endpoint shall be of type CLIENT or SERVER
   */
  if (host == NULL) {
    type = SOCKET_SERVER;
  } else if (type != SOCKET_CLIENT && type != SOCKET_SERVER) {
    return NULL;
  }

  /*
   * Initialize the endpoint address information
   * -------------------------------------------
   *  The AI_PASSIVE flag allows "wildcard address" when hostname is not
   *  provided (NULL). The wildcard address is used by applications (ty-
   *  pically servers) that intend to accept connections on any of the
   *  hosts's network addresses. If the hostname is not NULL, then the
   *  AI_PASSIVE flag is ignored.
   *  When the AI_V4MAPPED flag is set, and AF_INET6 is specified, and no
   *  matching IPv6 addresses could be found, then IPv4-mapped IPv6 addresses
   *  are returned by getaddrinfo in the list pointed to by result.
   */
  memset (&socket_info, 0, sizeof(struct addrinfo));
  socket_info.ai_socktype = SOCK_DGRAM; /* Datagram socket   */
  socket_info.ai_flags = AI_NUMERICSERV; /* numeric port number  */

  if (type != SOCKET_CLIENT) {
    /* Setup socket address options at the server side */
    socket_info.ai_family = AF_INET6; /* Accept either IPv4 or IPv6
         * connections     */
    socket_info.ai_flags |= AI_PASSIVE; /* Use "wildcard address"  */
    socket_info.ai_flags |= AI_V4MAPPED; /* IPv4-mapped IPv6 address */
  } else {
    /* Setup socket address options at the client side */
    socket_info.ai_family = AF_INET; /* Any address family   */
    //         socket_info.ai_flags |= AI_V4MAPPED; /* IPv4-mapped IPv6 address */
  }

  /*
   * getaddrinfo() returns a linked list of address structures:
   *  - The network host may be multi-homed, accessible over multiple
   *    protocols (e.g. both AF_INET and AF_INET6);
   *  - The same service (port number) may be available from multiple
   *    socket types (one SOCK_STREAM address and another SOCK_DGRAM address);
   */
  int rc = getaddrinfo (host, port, &socket_info, &socket_addr);

  if (rc != 0) {
    if (rc != EAI_SYSTEM) {
      errno = rc;
    }

    return NULL;
  }

  /*
   * Try each address until we successfully connect
   */
  for (sp = socket_addr; sp != NULL; sp = sp->ai_next) {
    /* Create the socket endpoint for communication */
    sfd = socket (sp->ai_family, sp->ai_socktype, sp->ai_protocol);

    if (sfd < 0) {
      continue;
    }

    /*
     * Initiate a communication channel at the CLIENT side
     */
    if (type == SOCKET_CLIENT) {
      /* Connect the socket to the remote server's address */
      if (connect (sfd, sp->ai_addr, sp->ai_addrlen) != -1) {
        break; /* Connection succeed */
      }
    }

    /*
     * Initiate a communication channel at the SERVER side
     */
    else {
      if (type == SOCKET_SERVER) {
        /* Set socket options */
        if (_socket_set_option (sfd) != RETURNok) {
          continue;
        }

        /* Bind the socket to the local server's address */
        if (bind (sfd, sp->ai_addr, sp->ai_addrlen) != -1) {
          break; /* Bind succeed */
        }
      }
    }

    close (sfd);
  }

  /* Free the memory that was dynamically allocated for the linked list */
  freeaddrinfo (socket_addr);

  if (sp == NULL) {
    /* Connect or bind failed */
    return NULL;
  }

  /* The connection endpoint has been successfully setup */
  socket_id_t * sid = (socket_id_t *) malloc (sizeof(struct socket_id_s));

  if (sid != NULL) {
    sid->type = type;
    sid->port = atoi (port);
    sid->fd = sfd;
  }

  return sid;
}

/****************************************************************************
 **                                                                        **
 ** Name:  socket_close()                                            **
 **                                                                        **
 ** Description: Cleanup the specified communication endpoint: Close the   **
 **    socket file descriptor and frees all the memory space     **
 **    allocated to handle the communication channel towards the **
 **    remote peer                                               **
 **                                                                        **
 ** Inputs:  None                                                      **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   id:    The identifier of the connection endpoint  **
 **      Return:  None                                       **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
void socket_close(void* id)
{
  if (id) {
    close (((socket_id_t*) id)->fd);
    free (id);
  }
}

/****************************************************************************
 **                                                                        **
 ** Name:  socket_recv()                                             **
 **                                                                        **
 ** Description: Receives data from a given communication endpoint to a    **
 **    receive buffer of specified length                        **
 **                                                                        **
 ** Inputs:  id:    The identifier of the connection endpoint  **
 **      length:  Length of the receive buffer               **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   id:    The identifier of the connection endpoint  **
 **      buffer:  The receive buffer                         **
 **      Return:  The number of bytes received when success; **
 **       RETURNerror otherwise.                     **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
ssize_t socket_recv(void* id, char* buffer, size_t length)
{
  socket_id_t* sid = (socket_id_t*) (id);
  ssize_t rbytes = -1;

  if (sid->type == SOCKET_CLIENT) {
    /* Receive data from the connected socket */
    rbytes = recv (sid->fd, buffer, length, 0);
  } else if (sid->type == SOCKET_SERVER) {
    struct sockaddr_storage addr;
    socklen_t addrlen = sizeof(addr);

    /* Receive data from the socket and retreive the remote host address */
    rbytes = recvfrom (sid->fd, buffer, length, 0, (struct sockaddr *) &addr, &addrlen);
    sid->addr = addr;
  }

  if (errno == EINTR) {
    /* A signal was caught */
    return 0;
  } else if (rbytes < 0) {
    /* Receive failed */
    return RETURNerror;
  }

  return rbytes;
}

/****************************************************************************
 **                                                                        **
 ** Name:  socket_send()                                             **
 **                                                                        **
 ** Description: Sends data to a given communication endpoint from a send  **
 **    buffer of specified length                                **
 **                                                                        **
 ** Inputs:  id:    The identifier of the connection endpoint  **
 **    buffer:  The send buffer                            **
 **      length:  Length of the send buffer                  **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  The number of bytes sent when success;     **
 **       RETURNerror otherwise.                     **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
ssize_t socket_send(const void* id, const char* buffer, size_t length)
{
  const socket_id_t* sid = (socket_id_t*) (id);
  ssize_t sbytes = -1;

  if (sid->type == SOCKET_CLIENT) {
    /* Send data to the connected socket */
    sbytes = send (sid->fd, buffer, length, 0);
  } else if (sid->type == SOCKET_SERVER) {
    /* Send data to the socket using the remote host address */
    sbytes = sendto (sid->fd, buffer, length, 0, (struct sockaddr *) &sid->addr, (socklen_t) sizeof(sid->addr));
  }

  if (errno == EINTR) {
    /* A signal was caught */
    return 0;
  } else if (sbytes != length) {
    /* Send failed */
    return RETURNerror;
  }

  return sbytes;
}

/****************************************************************************
 **                                                                        **
 ** Name:  socket_get_fd()                                           **
 **                                                                        **
 ** Description: Get the value of the socket file descriptor created for   **
 **    the given connection endpoint                             **
 **                                                                        **
 ** Inputs:  id:    The identifier of the connection endpoint  **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  The file descriptor of the socket created  **
 **       for this connection endpoint               **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
int socket_get_fd(const void* id)
{
  if (id) {
    return ((socket_id_t*) id)->fd;
  }

  return RETURNerror;
}

/****************************************************************************/
/*********************  L O C A L    F U N C T I O N S  *********************/
/****************************************************************************/

/****************************************************************************
 **                                                                        **
 ** Name:  _socket_set_option()                                      **
 **                                                                        **
 ** Description: Set socket option at the sockets API level (SOL_SOCKET)   **
 **                                                                        **
 ** Inputs:  sfd:   Socket file descriptor                     **
 **      Others:  None                                       **
 **                                                                        **
 ** Outputs:   None                                                      **
 **      Return:  RETURNok, RETURNerror                      **
 **      Others:  None                                       **
 **                                                                        **
 ***************************************************************************/
static int _socket_set_option(int sfd)
{
  int optval;

  /* SO_REUSEADDR socket option:
   * ---------------------------
   * Allows the server to bind a socket to this port, unless
   * there is an active listening socket already bound to the
   * port. This is useful when recovering from a crash and the
   * socket was not properly closed. The server can be restarted
   * and it will simply open another socket on the same port and
   * continue listening.
   */
  optval = TRUE;

  if (setsockopt (sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
    return RETURNerror;
  }

  /* IPV6_V6ONLY socket option
   * -------------------------
   * When option is set to TRUE, the socket is restricted to sending and
   * receiving IPv6 packets only.
   * When option is set to FALSE, the socket can be used to send and receive
   * packets to and from an IPv6 address or an IPv4-mapped IPv6 address.
   */
  optval = FALSE;

  if (setsockopt (sfd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) < 0) {
    return RETURNerror;
  }

  return RETURNok;
}

