/********************************************************************/
/*                                                                  */
/*            L   I  QQ  U U I DD    W   W  A  RR    555            */
/*            L   I Q  Q U U I D D   W   W A A R R   5              */
/*            L   I Q  Q U U I D D   W W W AAA RR    55             */
/*            L   I Q Q  U U I D D   WW WW A A R R     5            */
/*            LLL I  Q Q  U  I DD    W   W A A R R   55             */
/*                                                                  */
/*                             b                                    */
/*                             bb  y y                              */
/*                             b b yyy                              */
/*                             bb    y                              */
/*                                 yy                               */
/*                                                                  */
/*                     U U       FFF  O   O  TTT                    */
/*                     U U       F   O O O O  T                     */
/*                     U U TIRET FF  O O O O  T                     */
/*                     U U       F   O O O O  T                     */
/*                      U        F    O   O   T                     */
/*                                                                  */
/********************************************************************/

/*****************************************************************************/
/* Liquid War is a multiplayer wargame                                       */
/* Copyright (C) 1998-2005 Christian Mauduit                                 */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation; either version 2 of the License, or         */
/* (at your option) any later version.                                       */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
/*                                                                           */
/* Liquid War homepage : http://www.ufoot.org/liquidwar                      */
/* Contact author      : ufoot@ufoot.org                                     */
/*****************************************************************************/

/********************************************************************/
/* name          : sockw32.c                                        */
/* content       : simple wrappers on the winsock API               */
/* last update   : April 13th 2001                                  */
/********************************************************************/

/*==================================================================*/
/* includes                                                         */
/*==================================================================*/

#ifdef WIN32
#include <winsock.h>
#else
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#endif

#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "sockgen.h"
#include "log.h"

/*==================================================================*/
/* defines                                                          */
/*==================================================================*/

#define LW_SOCK_RECV_SEC                  8
#define LW_SOCK_RECV_USEC                 0
#define LW_SOCK_SEND_SEC                  8
#define LW_SOCK_SEND_USEC                 0

#define LW_SOCK_SEND_BUFFER_SIZE        100
#define LW_SOCK_RECV_BUFFER_SIZE        200

/*==================================================================*/
/* macros                                                           */
/*==================================================================*/

#ifndef MIN
#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
#endif

/*==================================================================*/
/* globals                                                          */
/*==================================================================*/

/*==================================================================*/
/* static fonctions                                                 */
/*==================================================================*/

static void clean_buffer (char *buffer, int len);

/*==================================================================*/
/* fonctions                                                        */
/*==================================================================*/

/*------------------------------------------------------------------*/
/*
 * Cleans up a message buffer from all characters which are not
 * plain standard ASCII. Used for logging information.
 */
static void
clean_buffer (char *buffer, int len)
{
  int i;

  for (i = 0; i < len; ++i)
    {
      if (!isalnum (buffer[i]) && buffer[i] != '-' && buffer[i] != '+')
	{
	  buffer[i] = '.';
	}
    }
}

/*------------------------------------------------------------------*/
/*
 * Returns true if the given number bytes is available on the socket
 */
int
lw_sock_peek_ex (int *sock, int len)
{
  int result = 0;
  int res;
  fd_set read;
  struct timeval tv;
  char buffer[LW_SOCK_MESSAGE_SIZE];
  int sock_copy;

  if ((sock_copy = *sock) >= 0)
    {
      if (len <= LW_SOCK_MESSAGE_SIZE - 1)
	{
	  FD_ZERO (&read);
	  FD_SET (sock_copy, &read);
	  tv.tv_sec = 0;
	  tv.tv_usec = 0;
	  res = select (sock_copy + 1, &read, NULL, NULL, &tv);
	  if (res > 0)
	    {
	      if (FD_ISSET (sock_copy, &read))
		{
		  /* 
		   * First we test if there's enough data available
		   */
		  if (recv (sock_copy, buffer, len, MSG_PEEK) == len)
		    {
		      result = 1;
		    }
		}
	    }
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Sends a string on the network.
 * The advantage of this function over a raw "send" is that it does
 * a "strlen" automatically to know the length of the string, and
 * adds a tailing CR+LF so that the message is "telnet/http compliant"
 */
int
lw_sock_send_str_ex (int *sock, char *str)
{
  int result = 0;
  int len;
  char buffer[LW_SOCK_MESSAGE_SIZE];
  int sock_copy;
  fd_set write;
  struct timeval tv;
  int res;
  int total_sent;
  int sent;
#ifdef WIN32
  int winerr;
#endif

  if ((sock_copy = *sock) >= 0)
    {
      result = 1;

      /*
       * We put the string in a buffer, since we'll probably have
       * to modify it (cut if it's too long, add a tailing CR+LF).
       */
      len = strlen (str);
      len = MIN (len, LW_SOCK_MESSAGE_SIZE - 3);
      strncpy (buffer, str, len);
      buffer[len] = '\x0d';
      ++len;
      buffer[len] = '\x0a';
      ++len;
      buffer[len] = '\0';

      total_sent = 0;
      while (total_sent < len && result && (sock_copy = *sock) >= 0)
	{
	  FD_ZERO (&write);
	  FD_SET (sock_copy, &write);
	  tv.tv_sec = LW_SOCK_SEND_SEC;
	  tv.tv_usec = LW_SOCK_SEND_USEC;

	  errno = 0;
	  res = select (sock_copy + 1, NULL, &write, NULL, &tv);

	  switch (res)
	    {
	    case -1:
#ifdef WIN32
	      winerr = WSAGetLastError ();
	      if (winerr != WSAEINTR && winerr != WSAENOBUFS)
		{
		  result = 0;
		}
#else
	      if (errno != EINTR && errno != ENOBUFS)
		{
		  result = 0;
		}
#endif
	      break;
	    case 1:
	      if (FD_ISSET (sock_copy, &write))
		{
		  sent =
		    send (sock_copy, buffer + total_sent, len - total_sent,
			  0);
		  if (sent > 0 && sent <= len - total_sent)
		    {
		      total_sent += sent;
		    }
		  else
		    {
		      result = 0;
		    }
		}
	      break;
	    default:
	      result = 0;
	    }
	}

      if (LW_SOCK_LOG)
	{
	  switch (result)
	    {
	    case 1:
	      log_print_int (sock_copy);
	      log_print_str (" > \"");
	      log_print_str (str);
	      log_print_str ("\"");
	      log_println ();
	      log_flush ();
	      break;
	    case 0:
	      log_print_int (sock_copy);
	      log_print_str (" > timeout!");
	      log_println ();
	      break;
	    default:
	      log_print_int (sock_copy);
	      log_print_str (" > unexpected error!");
	      log_println ();
	      break;
	    }
	}
    }
  return result;
}

/*------------------------------------------------------------------*/
/*
 * Receives some data from the network.
 * A tailing "\n" is expected. The routine handles both chr(10) and chr(13)
 * correctly so that the program can be used with telnet under UNIX or
 * windows without much trouble.
 * This tailing "\n" is removed, so the return string is just the exact
 * message which has been send with sock_send.
 * Buffer must be able to accept at least LW_SOCK_MESSAGE_SIZE chars.
 * Note that the function will not block forever, if there's no incoming
 * for more than a #define specified amount of time, it will return
 * an error.
 */
int
lw_sock_recv_str_ex (int *sock, char *str)
{
  int result = 0;
  int pos, l;
  int cr_found;
  char *cr;
  fd_set read;
  int sock_copy;
  struct timeval tv;
  int res;
#ifdef WIN32
  int winerr;
#endif

  str[0] = '\0';

  if ((sock_copy = *sock) >= 0)
    {
      result = 1;

      /*
       * We keep on trying to get data until
       * - we get a '\n' character
       * - the size of incoming data exceeds LW_SOCK_MESSAGE_SIZE
       * - there's a reception low level error
       */
      cr_found = 0;
      pos = 0;
      while (!cr_found && pos < LW_SOCK_MESSAGE_SIZE - 1 && result > 0
	     && (sock_copy = *sock) >= 0)
	{
	  FD_ZERO (&read);
	  FD_SET (sock_copy, &read);
	  tv.tv_sec = LW_SOCK_RECV_SEC;
	  tv.tv_usec = LW_SOCK_RECV_USEC;

	  errno = 0;
	  res = select (sock_copy + 1, &read, NULL, NULL, &tv);

	  switch (res)
	    {
	    case -1:
#ifdef WIN32
	      winerr = WSAGetLastError ();
	      if (winerr != WSAEINTR)
		{
		  result = 0;
		}
#else
	      if (errno != EINTR)
		{
		  result = 0;
		}
#endif
	      break;
	    case 1:
	      /*
	       * We check that the event we just received concerns the socket
	       * we are polling. If the event is not for us, let's consider
	       * everything is fine...
	       */
	      if (!FD_ISSET (sock_copy, &read))
		{
		  result = 1;
		}
	      else
		{
		  /*
		   * We get the caracters one by one. This is a performance
		   * killer but we don't care since this routine is only
		   * used at the very beginning of the game, when the players
		   * are connecting themselves. And it has the *big* advantage
		   * that "netkeys" are not eaten up by this routine. More
		   * precisely, there's used to be a bug because when reading
		   * the final "OK" message, the clients read some netkeys
		   * along with it, which caused some network inconsistency
		   * since the messages had "disappeared".
		   */
		  if ((l = recv (sock_copy, str + pos, 1, 0)) <= 0)
		    {
		      /*
		       * OK, now we have received a message on this socket
		       * but there's no data. In most of the cases this means
		       * the socket is dead, so we return -1 instead of 0
		       * so that the caller can make the difference between
		       * a timeout (0) and a dead socket (-1).
		       */
		      result = -1;
		    }
		  else
		    {
		      /*
		       * pos is an offset in the buffer. It is used to keep a
		       * trace of where new data should be appended in case we
		       * have to call recv several times to retrieve the whole
		       * message.
		       */
		      pos += l;
		      /*
		       * We add a tailing '\0' so that the string is "C compliant"
		       */
		      str[pos] = 0;
		      /*
		       * We seek for character 10, which should be '\n'
		       */
		      if ((cr = strchr (str, '\x0a')) != NULL)
			{
			  cr_found = 1;
			  /*
			   * We handle the special character '13' for very often,
			   * especially when using telnet, the strings come
			   * with char(13)chr(10) at the end. So if it happens
			   * that after removing the 10 there's still a 13, then
			   * we remove the 13 as well.
			   */
			  if ((cr - str) >= 1 && (*(cr - 1)) == '\x0d')
			    {
			      /*
			       * Let's cut this ugly "ascii 13" character
			       * along with "ascii 10"
			       */
			      (*(cr - 1)) = 0;
			    }
			  else
			    {
			      /*
			       * No "ascii 13" in sight, simply remove "ascii 10"
			       */
			      (*cr) = 0;
			    }
			}
		      result = 1;
		    }
		}
	      break;
	    default:
	      result = 0;
	    }
	}

      if (LW_SOCK_LOG)
	{
	  switch (result)
	    {
	    case 1:
	      log_print_int (sock_copy);
	      log_print_str (" < \"");
	      log_print_str (str);
	      log_print_str ("\"");
	      log_println ();
	      log_flush ();
	      break;
	    case 0:
	      log_print_int (sock_copy);
	      log_print_str (" < timeout!");
	      log_println ();
	      break;
	    case -1:
	      log_print_int (sock_copy);
	      log_print_str (" < closed!");
	      log_println ();
	      break;
	    default:
	      log_print_int (sock_copy);
	      log_print_str (" < unexpected error!");
	      log_println ();
	      break;
	    }
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Sends a buffer on the network.
 * Only a standard send wrapper
 */
int
lw_sock_send_buffer_ex (int *sock, char *buffer, int len)
{
  int result = 0;
  char trace[LW_SOCK_MESSAGE_SIZE];
  fd_set write;
  struct timeval tv;
  int sock_copy;
  int res;
  int total_sent;
  int sent;
#ifdef WIN32
  int winerr;
#endif

  if ((sock_copy = *sock) >= 0)
    {
      result = 1;

      if (len <= LW_SOCK_MESSAGE_SIZE - 1)
	{
	  total_sent = 0;
	  while (total_sent < len && result && (sock_copy = *sock) >= 0)
	    {
	      FD_ZERO (&write);
	      FD_SET (sock_copy, &write);
	      tv.tv_sec = LW_SOCK_SEND_SEC;
	      tv.tv_usec = LW_SOCK_SEND_USEC;

	      errno = 0;
	      res = select (sock_copy + 1, NULL, &write, NULL, &tv);

	      switch (res)
		{
		case -1:
#ifdef WIN32
		  winerr = WSAGetLastError ();
		  if (winerr != WSAEINTR && winerr != WSAENOBUFS)
		    {
		      result = 0;
		    }
#else
		  if (errno != EINTR && errno != ENOBUFS)
		    {
		      result = 0;
		    }
#endif
		  break;
		case 1:
		  if (FD_ISSET (sock_copy, &write))
		    {
		      sent = send (sock_copy,
				   buffer + total_sent,
				   MIN (len - total_sent,
					LW_SOCK_SEND_BUFFER_SIZE), 0);
		      if (sent > 0 && sent <= len - total_sent)
			{
			  total_sent += sent;
			}
		      else
			{
			  result = 0;
			}
		    }
		  break;
		default:
		  result = 0;
		}
	    }

	  if (LW_SOCK_LOG)
	    {
	      if (result)
		{
		  strncpy (trace, buffer, len);
		  trace[len] = '\0';
		  log_print_int (sock_copy);
		  log_print_str (" > [");
		  clean_buffer (trace, len);
		  log_print_str (trace);
		  log_print_str ("]");
		  log_println ();
		  log_flush ();
		}
	      else
		{
		  log_print_int (sock_copy);
		  log_print_str (" > timeout!");
		  log_println ();
		}
	    }
	}
      else
	{
	  result = 0;
	  if (LW_SOCK_LOG)
	    {
	      log_print_int (sock_copy);
	      log_print_str (" > message too large!");
	      log_println ();
	    }
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Receives a buffer on the network.
 * Only a standard recv wrapper
 */
int
lw_sock_recv_buffer_ex (int *sock, char *buffer, int len)
{
  int result = 0;
  char trace[LW_SOCK_MESSAGE_SIZE];
  int res;
  fd_set read;
  struct timeval tv;
  int sock_copy;
  int total_received;
  int received;
#ifdef WIN32
  int winerr;
#endif

  memset (buffer, 0, len);

  if ((sock_copy = *sock) >= 0)
    {
      result = 1;

      if (len <= LW_SOCK_MESSAGE_SIZE - 1)
	{
	  total_received = 0;
	  while (total_received < len && result && (sock_copy = *sock) >= 0)
	    {
	      FD_ZERO (&read);
	      FD_SET (sock_copy, &read);
	      tv.tv_sec = LW_SOCK_RECV_SEC;
	      tv.tv_usec = LW_SOCK_RECV_USEC;

	      errno = 0;
	      res = select (sock_copy + 1, &read, NULL, NULL, &tv);

	      switch (res)
		{
		case -1:
#ifdef WIN32
		  winerr = WSAGetLastError ();
		  if (winerr != WSAEINTR)
		    {
		      result = 0;
		    }
#else
		  if (errno != EINTR)
		    {
		      result = 0;
		    }
#endif
		  break;
		case 1:
		  if (FD_ISSET (sock_copy, &read))
		    {
		      received = recv (sock_copy,
				       buffer + total_received,
				       MIN (len - total_received,
					    LW_SOCK_RECV_BUFFER_SIZE), 0);
		      if (received > 0 && received <= len - total_received)
			{
			  total_received += received;
			}
		      else
			{
			  result = 0;
			}
		    }
		  break;
		default:
		  result = 0;
		}
	    }

	  if (LW_SOCK_LOG)
	    {
	      if (result)
		{
		  strncpy (trace, buffer, len);
		  trace[len] = '\0';
		  log_print_int (sock_copy);
		  log_print_str (" < [");
		  clean_buffer (trace, len);
		  log_print_str (trace);
		  log_print_str ("]");
		  log_println ();
		  log_flush ();
		}
	      else
		{
		  log_print_int (sock_copy);
		  log_print_str (" < timeout!");
		  log_println ();
		}
	    }
	}
      else
	{
	  result = 0;
	  if (LW_SOCK_LOG)
	    {
	      log_print_int (sock_copy);
	      log_print_str (" < message too large!");
	      log_println ();
	    }
	}
    }

  return result;
}
