/* GNet - Networking library
 * Copyright (C) 2000-3  David Helder
 * Copyright (C) 2000  Andrew Lanoix
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

/*
 *  2003-10-08 Hiroyuki Ikezoe <poincare@ikezoe.net>
 *     Deleted Server stuff.
 *     Deleted gnet_socks_get_enabled().
 */

#include "gnet-private.h"
#include "tcp.h"

/**
 *  gnet_tcp_socket_connect
 *  @hostname: host name
 *  @port: port
 *
 *  Creates a #GTcpSocket and connects to @hostname:@port.  This
 *  function blocks (while gnet_tcp_socket_connect_async() does not).
 *  To get the #GInetAddr of the #GTcpSocket, call
 *  gnet_tcp_socket_get_remote_inetaddr().
 *
 *  Returns: a new #GTcpSocket; NULL on error.
 **/
GTcpSocket*
gnet_tcp_socket_connect (const gchar* hostname, gint port)
{
  GList* ia_list;
  GList* i;
  GTcpSocket* socket;

  ia_list = gnet_inetaddr_new_list (hostname, port);
  if (ia_list == NULL)
    return NULL;

  socket = NULL;
  for (i = ia_list; i != NULL; i = i->next)
    {
      GInetAddr* ia;

      ia = (GInetAddr*) i->data;
      socket = gnet_tcp_socket_new (ia);
      if (socket)
	break;
    }
  for (i = ia_list; i != NULL; i = i->next)
    gnet_inetaddr_delete ((GInetAddr*) i->data);
  g_list_free (ia_list);

  return socket;
}


/**
 *  gnet_tcp_socket_connect_async
 *  @hostname: host name
 *  @port: port
 *  @func: callback function
 *  @data: data to pass to @func on callback
 *
 *  Asynchronously creates a #GTcpSocket and connects to
 *  @hostname:@port.  The callback is called when the connection is
 *  made or an error occurs.  The callback will not be called during
 *  the call to this function.
 *
 *  Returns: the ID of the connection; NULL on failure.  The ID can be
 *  used with gnet_tcp_socket_connect_async_cancel() to cancel the
 *  connection.
 *
 **/
GTcpSocketConnectAsyncID
gnet_tcp_socket_connect_async (const gchar* hostname, gint port, 
			       GTcpSocketConnectAsyncFunc func, 
			       gpointer data)
{
  GTcpSocketConnectState* state;

  g_return_val_if_fail(hostname != NULL, NULL);
  g_return_val_if_fail(func != NULL, NULL);

  state = g_new0(GTcpSocketConnectState, 1);
  state->func = func;
  state->data = data;

  state->inetaddr_id = 
    gnet_inetaddr_new_list_async (hostname, port,  
				  gnet_tcp_socket_connect_inetaddr_cb, 
				  state);

  /* On failure, gnet_inetaddr_new_list_async() returns NULL.  It will
     not call the callback before it returns. */
  if (state->inetaddr_id == NULL)
    {
      g_free (state);
      return NULL;
    }

  return state;
}



void
gnet_tcp_socket_connect_inetaddr_cb (GList* ia_list, gpointer data)
{
  GTcpSocketConnectState* state = (GTcpSocketConnectState*) data;

  if (ia_list != NULL) /* Success */
    {
      g_assert (ia_list);

      state->inetaddr_id = NULL;
      state->ia_list = ia_list;
      state->ia_next = ia_list;

      while (state->ia_next != NULL)
	{
	  GInetAddr* ia;
	  gpointer tcp_id;

	  ia = (GInetAddr*) state->ia_next->data;
	  state->ia_next = state->ia_next->next;

	  tcp_id = gnet_tcp_socket_new_async (ia, 
					      gnet_tcp_socket_connect_tcp_cb, 
					      state);
	  if (tcp_id)	/* Success */
	    {
	      state->tcp_id = tcp_id;
	      return;
	    }
	}

      /* Failure: We could not async connect to any address.
         In practice, new_async() rarely fails immediately.  */
      state->in_callback = TRUE;
      (*state->func)(NULL, GTCP_SOCKET_CONNECT_ASYNC_STATUS_INETADDR_ERROR, 
		     state->data);
      state->in_callback = FALSE;

      gnet_tcp_socket_connect_async_cancel (state);
    }
  else /* Failure */
    {
      state->in_callback = TRUE;
      (*state->func)(NULL, GTCP_SOCKET_CONNECT_ASYNC_STATUS_INETADDR_ERROR, 
		     state->data);
      state->in_callback = FALSE;

      gnet_tcp_socket_connect_async_cancel (state);
    }
}


void 
gnet_tcp_socket_connect_tcp_cb (GTcpSocket* socket, gpointer data)
{
  GTcpSocketConnectState* state = (GTcpSocketConnectState*) data;

  g_return_if_fail (state != NULL);

  state->tcp_id = NULL;

  /* Success */
  if (socket != NULL)
    {
      state->in_callback = TRUE;
      (*state->func)(socket, GTCP_SOCKET_CONNECT_ASYNC_STATUS_OK, state->data);
      state->in_callback = FALSE;

      gnet_tcp_socket_connect_async_cancel (state);

      return;
    }

  /* Failure: Could not connect to address */

  /* Try other addresses */
  while (state->ia_next != NULL)
    {
      GInetAddr* ia;
      gpointer tcp_id = NULL;

      ia = (GInetAddr*) state->ia_next->data;
      state->ia_next = state->ia_next->next;

      tcp_id = gnet_tcp_socket_new_async (ia, 
					  gnet_tcp_socket_connect_tcp_cb, 
					  state);
      if (tcp_id)	/* Success */
	{
	  state->tcp_id = tcp_id;
	  return;
	}
    }

  /* Failure: No more addresses */
  state->in_callback = TRUE;
  (*state->func)(NULL, GTCP_SOCKET_CONNECT_ASYNC_STATUS_TCP_ERROR, state->data);
  state->in_callback = FALSE;

  gnet_tcp_socket_connect_async_cancel (state);
}


/**
 *  gnet_tcp_socket_connect_async_cancel
 *  @id: ID of the connection
 *
 *  Cancels an asynchronous connection that was started with
 *  gnet_tcp_socket_connect_async().
 * 
 */
void
gnet_tcp_socket_connect_async_cancel (GTcpSocketConnectAsyncID id)
{
  GTcpSocketConnectState* state = (GTcpSocketConnectState*) id;

  g_return_if_fail (state != NULL);

  /* Ignore if user called in the middle of a callback */
  if (state->in_callback)
    return;

  if (state->ia_list)
    {
      GList* i;
      
      for (i = state->ia_list; i != NULL; i = i->next)
	gnet_inetaddr_delete ((GInetAddr*) i->data);
      g_list_free (state->ia_list);
    }

  if (state->inetaddr_id)
    {
      gnet_inetaddr_new_async_cancel(state->inetaddr_id);
    }

  if (state->tcp_id)
    {
      gnet_tcp_socket_new_async_cancel (state->tcp_id);
    }

  g_free (state);
}


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


/**
 *  gnet_tcp_socket_new
 *  @addr: address
 *
 *  Creates a #GTcpSocket and connects to @addr.  This function
 *  blocks.  SOCKS is used if SOCKS is enabled.
 *
 *  Returns: a new #GTcpSocket; NULL on error.
 *
 **/
GTcpSocket* 
gnet_tcp_socket_new (const GInetAddr* addr)
{
  g_return_val_if_fail (addr != NULL, NULL);

  /* Otherwise, connect directly to the address */
  return gnet_tcp_socket_new_direct (addr);
}



/**
 *  gnet_tcp_socket_new_direct:
 *  @addr: address
 *
 *  Creates a #GTcpSocket and connects to @addr without using SOCKS.
 *  This function blocks.  Most users should use
 *  gnet_tcp_socket_new().
 *
 *  Returns: a new #GTcpSocket; NULL on error.
 *
 **/
GTcpSocket* 
gnet_tcp_socket_new_direct (const GInetAddr* addr)
{
  int 			sockfd;
  GTcpSocket* 		s;
  int			rv;

  g_return_val_if_fail (addr != NULL, NULL);

  /* Create socket */
  sockfd = socket (GNET_INETADDR_FAMILY(addr), SOCK_STREAM, 0);
  if (sockfd < 0)
    return NULL;

  /* Create GTcpSocket */
  s = g_new0 (GTcpSocket, 1);
  s->sockfd = sockfd;
  s->ref_count = 1;
  s->sa = addr->sa;

  /* Connect */
  rv = connect(sockfd, 
	       &GNET_SOCKADDR_SA(s->sa), GNET_SOCKADDR_LEN(s->sa));
  if (rv != 0)
    {
      GNET_CLOSE_SOCKET(s->sockfd);
      g_free (s);
      return NULL;
    }

  return s;
}


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


/**
 *  gnet_tcp_socket_new_async
 *  @addr: address
 *  @func: callback function
 *  @data: data to pass to @func on callback
 *
 *  Asynchronously creates a #GTcpSocket and connects to @addr.  The
 *  callback is called once the connection is made or an error occurs.
 *  The callback will not be called during the call to this function.
 *
 *  SOCKS is used if SOCKS is enabled.  The SOCKS negotiation will
 *  block.
 *
 *  Returns: the ID of the connection; NULL on failure.  The ID can be
 *  used with gnet_tcp_socket_new_async_cancel() to cancel the
 *  connection.
 *
 **/
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async (const GInetAddr* addr, 
			   GTcpSocketNewAsyncFunc func,
			   gpointer data)
{
  g_return_val_if_fail (addr != NULL, NULL);
  g_return_val_if_fail (func != NULL, NULL);

  /* Otherwise, connect directly to the address */
  return gnet_tcp_socket_new_async_direct (addr, func, data);
}


#ifndef GNET_WIN32  /*********** Unix code ***********/


/**
 *  gnet_tcp_socket_new_async_direct
 *  @addr: address
 *  @func: callback function
 *  @data: data to pass to @func on callback
 *
 *  Asynchronously creates a #GTcpSocket and connects to @addr without
 *  using SOCKS.  Most users should use gnet_tcp_socket_new_async()
 *  instead.  The callback is called once the connection is made or an
 *  error occurs.  The callback will not be called during the call to
 *  this function.
 *
 *  Returns: the ID of the connection; NULL on failure.  The ID can be
 *  used with gnet_tcp_socket_new_async_cancel() to cancel the
 *  connection.
 *
 **/
GTcpSocketNewAsyncID
gnet_tcp_socket_new_async_direct (const GInetAddr* addr, 
				  GTcpSocketNewAsyncFunc func,
				  gpointer data)
{
  gint 			sockfd;
  gint 			flags;
  GTcpSocket* 		s;
  GTcpSocketAsyncState* state;

  g_return_val_if_fail(addr != NULL, NULL);
  g_return_val_if_fail(func != NULL, NULL);

  /* Create socket */
  sockfd = socket(GNET_INETADDR_FAMILY(addr), SOCK_STREAM, 0);
  if (sockfd < 0)
    return NULL;

  /* Get the flags (should all be 0?) */
  flags = fcntl(sockfd, F_GETFL, 0);
  if (flags == -1)
    return NULL;

  if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
    return NULL;

  /* Create our structure */
  s = g_new0(GTcpSocket, 1);
  s->ref_count = 1;
  s->sockfd = sockfd;

  /* Connect (but non-blocking!) */
  if (connect(s->sockfd, &GNET_INETADDR_SA(addr), 
	      GNET_INETADDR_LEN(addr)) < 0)
    {
      if (errno != EINPROGRESS)
	{
	  g_free(s);
	  return NULL;
	}
    }

  /* Save address */ 
  s->sa = addr->sa;

  /* Note that if connect returns 0, then we're already connected and
     we could call the call back immediately.  But, it would probably
     make things too complicated for the user if we could call the
     callback before we returned from this function.  */

  /* Wait for the connection */
  state = g_new0(GTcpSocketAsyncState, 1);
  state->socket = s;
  state->func = func;
  state->data = data;
  state->flags = flags;
  state->iochannel = gnet_private_io_channel_new(s->sockfd);
  state->connect_watch = g_io_add_watch(state->iochannel,
					GNET_ANY_IO_CONDITION,
					gnet_tcp_socket_new_async_cb, 
					state);

  return state;
}


gboolean 
gnet_tcp_socket_new_async_cb (GIOChannel* iochannel, 
			      GIOCondition condition, 
			      gpointer data)
{
  GTcpSocketAsyncState* state = (GTcpSocketAsyncState*) data;
  gint error;
  socklen_t len;

  g_source_remove (state->connect_watch);
  state->connect_watch = 0;
  g_io_channel_unref (state->iochannel);
  state->iochannel = NULL;

  errno = 0;
  if (!((condition & G_IO_IN) || (condition & G_IO_OUT)))
    goto error;

  len = sizeof(error);

  /* Get the error option */
  if (getsockopt(state->socket->sockfd, SOL_SOCKET, SO_ERROR, (void*) &error, &len) < 0)
    goto error;

  /* Check if there is an error */
  if (error)
    goto error;

  /* Reset the flags */
  if (fcntl(state->socket->sockfd, F_SETFL, state->flags) != 0)
    goto error;

  /* Success */
  (*state->func)(state->socket, state->data);
  g_free(state);
  return FALSE;

  /* Error */
 error:
  (*state->func)(NULL, state->data);
  gnet_tcp_socket_delete (state->socket);
  g_free(state);

  return FALSE;
}


/**
 *  gnet_tcp_socket_new_async_cancel
 *  @id: ID of the connection
 *
 *  Cancels an asynchronous #GTcpSocket creation that was started with
 *  gnet_tcp_socket_new_async().
 *
 **/
void
gnet_tcp_socket_new_async_cancel (GTcpSocketNewAsyncID id)
{
  GTcpSocketAsyncState* state = (GTcpSocketAsyncState*) id;

  if (state->connect_watch)
    g_source_remove(state->connect_watch);
  if (state->iochannel)
    g_io_channel_unref (state->iochannel);
  gnet_tcp_socket_delete (state->socket);
  g_free (state);
}

#else	/*********** Windows code ***********/


GTcpSocketNewAsyncID
gnet_tcp_socket_new_async_direct (const GInetAddr* addr,
				  GTcpSocketNewAsyncFunc func,
				  gpointer data)
{
  gint sockfd;
  gint status;
  GTcpSocket* s;
  GTcpSocketAsyncState* state;
  u_long arg;

  g_return_val_if_fail(addr != NULL, NULL);
  g_return_val_if_fail(func != NULL, NULL);

  /* Create socket */
  sockfd = socket(GNET_INETADDR_FAMILY(addr), SOCK_STREAM, 0);
  if (sockfd == INVALID_SOCKET)
      return NULL;
	
  /* Create our structure */
  s = g_new0(GTcpSocket, 1);
  s->ref_count = 1;
  s->sockfd = sockfd;

  /* Force the socket into non-blocking mode */
  arg = 1;
  ioctlsocket(sockfd, FIONBIO, &arg);

  status = connect(s->sockfd, &GNET_INETADDR_SA(addr), 
		   GNET_INETADDR_LEN(addr));
  if (status == SOCKET_ERROR) /* Returning an error is ok, unless.. */
    {
      status = WSAGetLastError();
      if (status != WSAEWOULDBLOCK)
	{
	  g_free(s);
	  return NULL;
	}
    }

  /* Save address */ 
  s->sa = addr->sa;

  /* Wait for the connection */
  state = g_new0(GTcpSocketAsyncState, 1);
  state->socket = s;
  state->func = func;
  state->data = data;
  state->socket->sockfd = sockfd;

  state->connect_watch = 
    g_io_add_watch(gnet_private_io_channel_new(s->sockfd),
		   G_IO_IN | G_IO_ERR,
		   gnet_tcp_socket_new_async_cb, 
		   state);

  if (state->connect_watch <= 0)
      return NULL;

  return state;
}



gboolean
gnet_tcp_socket_new_async_cb (GIOChannel* iochannel,
			      GIOCondition condition,
			      gpointer data)
{
  GTcpSocketAsyncState* state = (GTcpSocketAsyncState*) data;

  if (condition & G_IO_ERR)
    goto error;

  (*state->func)(state->socket, state->data);
  g_free (state);
  return FALSE;

 error:
  (*state->func)(NULL, state->data);
  gnet_tcp_socket_delete (state->socket);
  g_free (state);
  return FALSE;
}


void
gnet_tcp_socket_new_async_cancel (GTcpSocketNewAsyncID id)
{
  GTcpSocketAsyncState* state = (GTcpSocketAsyncState*) id;
	
  g_source_remove(state->connect_watch);
  gnet_tcp_socket_delete(state->socket);
  g_free (state);
}

#endif		/*********** End Windows code ***********/



/**
 *  gnet_tcp_socket_delete
 *  @socket: a #GTcpSocket
 *
 *  Deletes a #GTcpSocket.
 *
 **/
void
gnet_tcp_socket_delete (GTcpSocket* socket)
{
  if (socket != NULL)
    gnet_tcp_socket_unref (socket);
}



/**
 *  gnet_tcp_socket_ref
 *  @socket: a #GTcpSocket
 *
 *  Adds a reference to a #GTcpSocket.
 *
 **/
void
gnet_tcp_socket_ref (GTcpSocket* socket)
{
  g_return_if_fail (socket != NULL);

  ++socket->ref_count;
}


/**
 *  gnet_tcp_socket_unref
 *  @socket: a #GTcpSocket to unreference
 *
 *  Removes a reference from a #GTcpSocket.  A #GTcpSocket is deleted
 *  when the reference count reaches 0.
 *
 **/
void
gnet_tcp_socket_unref (GTcpSocket* socket)
{
  g_return_if_fail (socket != NULL);

  socket->ref_count--;

  if (socket->ref_count == 0)
    {
      if (socket->accept_watch)
	g_source_remove (socket->accept_watch);

      GNET_CLOSE_SOCKET(socket->sockfd);/* Don't care if this fails... */

      if (socket->iochannel)
	g_io_channel_unref (socket->iochannel);

      g_free(socket);
    }
}



/**
 *  gnet_tcp_socket_get_io_channel
 *  @socket: a #GTcpSocket
 *
 *  Gets the #GIOChannel of a #GTcpSocket.
 *
 *  For a client socket, the #GIOChannel represents the data stream.
 *  Use it like you would any other #GIOChannel.
 *
 *  For a server socket however, the #GIOChannel represents the
 *  listening socket.  When it's readable, there's a connection
 *  waiting to be accepted.  However, using
 *  gnet_tcp_socket_server_accept_async() is more elegant than
 *  watching the #GIOChannel.
 *
 *  Every #GTcpSocket has one and only one #GIOChannel.  If you ref
 *  the channel, then you must unref it eventually.  Do not close the
 *  channel.  The channel is closed by GNet when the socket is
 *  deleted.
 *
 *  Returns: a #GIOChannel.
 *
 **/
GIOChannel* 
gnet_tcp_socket_get_io_channel (GTcpSocket* socket)
{
  g_return_val_if_fail (socket != NULL, NULL);

  if (socket->iochannel == NULL)
    socket->iochannel = gnet_private_io_channel_new(socket->sockfd);

  return socket->iochannel;
}


/**
 *  gnet_tcp_socket_get_remote_inetaddr
 *  @socket: a #GTcpSocket
 *
 *  Gets the address of the remote host from a #GTcpSocket.  This
 *  function does not work on server sockets.
 *
 *  Returns: a #GInetAddr.
 *
 **/
GInetAddr* 
gnet_tcp_socket_get_remote_inetaddr (const GTcpSocket* socket)
{
  GInetAddr* ia;

  g_return_val_if_fail (socket != NULL, NULL);

  ia = g_new0(GInetAddr, 1);
  ia->sa = socket->sa;
  ia->ref_count = 1;

  return ia;
}


/**
 *  gnet_tcp_socket_get_local_inetaddr
 *  @socket: a #GTcpSocket
 *
 *  Gets the local host's address from a #GTcpSocket.
 *
 *  Returns: a #GInetAddr.
 *
 **/
GInetAddr*  
gnet_tcp_socket_get_local_inetaddr (const GTcpSocket* socket)
{
  socklen_t socklen;
  struct sockaddr_storage sa;
  GInetAddr* ia;

  g_return_val_if_fail (socket, NULL);

  socklen = sizeof(sa);
  if (getsockname(socket->sockfd, &GNET_SOCKADDR_SA(sa), &socklen) != 0)
    return NULL;

  ia = g_new0(GInetAddr, 1);
  ia->ref_count = 1;
  ia->sa = sa;

  return ia;
}


/**
 *  gnet_tcp_socket_get_port
 *  @socket: a #GTcpSocket
 *
 *  Gets the port a server #GTcpSocket is bound to.
 *
 *  Returns: the port number.
 *
 **/
gint
gnet_tcp_socket_get_port (const GTcpSocket* socket)
{
  g_return_val_if_fail (socket != NULL, 0);

  return g_ntohs(GNET_SOCKADDR_PORT(socket->sa));
}



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

/**
 *  gnet_tcp_socket_set_tos
 *  @socket: a #GTcpSocket
 *  @tos: type of service
 *
 *  Sets the type-of-service (TOS) of the socket.  TOS theoretically
 *  controls the connection's quality of service, but most routers
 *  ignore it.  Some systems don't even support this function.  The
 *  function does nothing if the operating system does not support it.
 *
 **/
void
gnet_tcp_socket_set_tos (GTcpSocket* socket, GNetTOS tos)
{
  int sotos;

  g_return_if_fail (socket != NULL);

  /* Some systems (e.g. OpenBSD) do not have IPTOS_*.  Other systems
     have some of them, but not others.  And some systems have them,
     but with different names (e.g. FreeBSD has IPTOS_MINCOST).  If a
     system does not have a IPTOS, or any of them, then this function
     does nothing.  */
  switch (tos)
    {
#ifdef IPTOS_LOWDELAY
    case GNET_TOS_LOWDELAY:	sotos = IPTOS_LOWDELAY;		break;
#endif
#ifdef IPTOS_THROUGHPUT
    case GNET_TOS_THROUGHPUT:	sotos = IPTOS_THROUGHPUT;	break;
#endif
#ifdef IPTOS_RELIABILITY
    case GNET_TOS_RELIABILITY:	sotos = IPTOS_RELIABILITY;	break;
#endif
#ifdef IPTOS_LOWCOST
    case GNET_TOS_LOWCOST:	sotos = IPTOS_LOWCOST;		break;
#else
#ifdef IPTOS_MINCOST	/* Called MINCOST in FreeBSD 4.0 */
    case GNET_TOS_LOWCOST:	sotos = IPTOS_MINCOST;		break;
#endif
#endif
    default: return;
    }

#ifdef IP_TOS
  if (setsockopt(socket->sockfd, IPPROTO_IP, IP_TOS, (void*) &sotos, sizeof(sotos)) != 0)
    g_warning ("Can't set TOS on TCP socket\n");
#endif

}

