/* GNet - Networking library
 * Copyright (C) 2000-2003  David Helder
 * Copyright (C) 2000-2003  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.
 */

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


/**
 *  gnet_udp_socket_new
 *  
 *  Creates a #GUdpSocket bound to all interfaces and an arbitrary
 *  port.
 *
 *  Returns: a new #GUdpSocket; NULL on error.
 *
 **/
GUdpSocket* 
gnet_udp_socket_new (void)
{
  return gnet_udp_socket_new_full (NULL, 0);
}


/**
 *  gnet_udp_socket_new_with_port:
 *  @port: port to bind to (0 for an arbitrary port)
 * 
 *  Creates a #GUdpSocket bound to all interfaces and port @port.  If
 *  @port is 0, an arbitrary port will be used.
 *
 *  Returns: a new #GUdpSocket; NULL on error.
 *
 **/
GUdpSocket* 
gnet_udp_socket_new_with_port (gint port)
{
  return gnet_udp_socket_new_full (NULL, port);
}


/**
 *  gnet_udp_socket_new_full
 *  @iface: interface to bind to (NULL for all interfaces)
 *  @port: port to bind to (0 for an arbitrary port)
 * 
 *  Creates a #GUdpSocket bound to interface @iface and port @port.
 *  If @iface is NULL, all interfaces will be used.  If @port is 0, an
 *  arbitrary port will be used.
 *
 *  Returns: a new #GUdpSocket; NULL on error.
 *
 **/
GUdpSocket* 
gnet_udp_socket_new_full (const GInetAddr* iface, gint port)
{
  SOCKET 			  sockfd;
  struct sockaddr_storage sa;
  GUdpSocket* 		  s;
  const int 		  on = 1;

  /* Create sockfd and address */
  sockfd = gnet_private_create_listen_socket (SOCK_DGRAM, iface, port, &sa);
  if (!GNET_IS_SOCKET_VALID(sockfd))
    {
      g_warning ("socket() failed");
      return NULL;
    }

  /* Set broadcast option.  This allows the user to broadcast packets.
     It has no effect otherwise. */
  if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, 
		 (void*) &on, sizeof(on)) != 0)
    {
      g_warning ("setsockopt() failed");
      GNET_CLOSE_SOCKET(sockfd);
      return NULL;
    }

  /* Bind to the socket to some local address and port */
  if (bind(sockfd, &GNET_SOCKADDR_SA(sa), GNET_SOCKADDR_LEN(sa)) != 0)
    {
      GNET_CLOSE_SOCKET(sockfd);
      return NULL;
    }

  /* Create UDP socket */
  s = g_new0 (GUdpSocket, 1);
  s->sockfd = sockfd;
  s->sa = sa;
  s->ref_count = 1;

  return s;
}



/**
 *  gnet_udp_socket_delete:
 *  @socket: a #GUdpSocket
 *
 *  Deletes a #GUdpSocket.
 *
 **/
void
gnet_udp_socket_delete (GUdpSocket* socket)
{
  if (socket != NULL)
    gnet_udp_socket_unref (socket);
}



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

  socket->ref_count++;
}


/**
 *  gnet_udp_socket_unref
 *  @socket: a #GUdpSocket
 *
 *  Removes a reference from a #GUdpScoket.  A #GUdpSocket is deleted
 *  when the reference count reaches 0.
 *
 **/
void
gnet_udp_socket_unref (GUdpSocket* s)
{
  g_return_if_fail(s != NULL);

  s->ref_count--;

  if (s->ref_count == 0)
    {
      GNET_CLOSE_SOCKET(s->sockfd);	/* Don't care if this fails... */

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

      g_free(s);
    }
}



/**
 *  gnet_udp_socket_get_io_channel
 *  @socket: a #GUdpSocket
 *
 *  Gets the #GIOChannel of a #GUdpSocket.
 *
 *  Use the channel with g_io_add_watch() to check if the socket is
 *  readable or writable.  If the channel is readable, call
 *  gnet_udp_socket_receive() to receive a packet.  If the channel is
 *  writable, call gnet_udp_socket_send() to send a packet.  This is
 *  not a normal giochannel - do not read from or write to it.
 *
 *  Every #GUdpSocket 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_udp_socket_get_io_channel (GUdpSocket* 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_udp_socket_get_local_inetaddr
 *  @socket: a #GUdpSocket
 *
 *  Gets the local host's address from a #GUdpSocket.
 *
 *  Returns: a #GInetAddr.
 *
 **/
GInetAddr*  
gnet_udp_socket_get_local_inetaddr (const GUdpSocket* 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_udp_socket_send
 *  @socket: a #GUdpSocket
 *  @buffer: buffer to send
 *  @length: length of @buffer
 *  @dst: destination address
 *
 *  Sends data to a host using a #GUdpSocket.
 *
 *  Returns: 0 if successful; something else on error.
 *
 **/
gint 
gnet_udp_socket_send (GUdpSocket* socket, 
		      const gchar* buffer, gint length, 
		      const GInetAddr* dst)
{
  gint bytes_sent;
  struct sockaddr_storage sa;

  g_return_val_if_fail (socket, -1);
  g_return_val_if_fail (dst,    -1);

  /* Address of dst must be address of socket */
#ifdef HAVE_IPV6
  if (GNET_INETADDR_FAMILY(dst) != GNET_SOCKADDR_FAMILY(socket->sa))
    {
      /* If dst is IPv4, map to IPv6 */
      if (GNET_INETADDR_FAMILY(dst) == AF_INET && 
	  GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
	{
          sa.ss_family = AF_INET6;
	  GNET_SOCKADDR_SET_SS_LEN(sa);
          GNET_SOCKADDR_PORT_SET(sa, GNET_INETADDR_PORT(dst));
          GNET_SOCKADDR_ADDR32_SET(sa, 0, 0);
          GNET_SOCKADDR_ADDR32_SET(sa, 1, 0);
          GNET_SOCKADDR_ADDR32_SET(sa, 2, g_htonl(0xffff));
          GNET_SOCKADDR_ADDR32_SET(sa, 3, GNET_INETADDR_ADDR32(dst, 0));
	}

      /* If dst is IPv6, map to IPv4 if possible */
      else if (GNET_INETADDR_FAMILY(dst) == AF_INET6 &&
               GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET &&
               IN6_IS_ADDR_V4MAPPED(&GNET_INETADDR_SA6(dst).sin6_addr))
	{
          sa.ss_family = AF_INET;
	  GNET_SOCKADDR_SET_SS_LEN(sa);
          GNET_SOCKADDR_PORT_SET(sa, GNET_INETADDR_PORT(dst));
          GNET_SOCKADDR_ADDR32_SET(sa, 0, GNET_INETADDR_ADDR32(dst, 3));
	}
      else
        return -1;

    }
    /* Addresses match - just copy the address */
    else
#endif
      {
	sa = dst->sa;
      }


  bytes_sent = sendto (socket->sockfd,
                       (void*) buffer, length, 0,
                       (struct sockaddr*) &sa, GNET_SOCKADDR_LEN(sa));

  if (bytes_sent != length)
    return -1;

  return 0;
}



/**
 *  gnet_udp_socket_receive
 *  @socket: a #GUdpSocket
 *  @buffer: buffer to write to
 *  @length: length of @buffer
 *  @src: pointer to source address (optional)
 *
 *  Receives data using a #GUdpSocket.  If @src is set, the source
 *  address is stored in the location @src points to.  The address is
 *  caller owned.
 *
 *  Returns: the number of bytes received, -1 on error.
 *
 **/
gint 
gnet_udp_socket_receive (GUdpSocket* socket, 
			 gchar* buffer, gint length,
			 GInetAddr** src)
{
  gint bytes_received;
  struct sockaddr_storage sa;
  gint sa_len = sizeof(struct sockaddr_storage);

  g_return_val_if_fail (socket, -1);
  g_return_val_if_fail (buffer, -1);

  bytes_received = recvfrom (socket->sockfd, 
			     (void*) buffer, length, 
			     0, (struct sockaddr*) &sa, &sa_len);

  if (bytes_received == -1)
    return -1;

  if (src)
    {
      (*src) = g_new0 (GInetAddr, 1);
      (*src)->sa = sa;
      (*src)->ref_count = 1;
    }

  return bytes_received;
}


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


/**
 *  gnet_udp_socket_has_packet
 *  @socket: a #GUdpSocket
 *
 *  Tests if a #GUdpSocket has a packet waiting to be received.
 *
 *  Returns: TRUE if there is packet waiting, FALSE otherwise.
 *
 **/
gboolean
gnet_udp_socket_has_packet (const GUdpSocket* socket)
{
  fd_set readfds;
  struct timeval timeout = {0, 0};

  FD_ZERO (&readfds);
  FD_SET (socket->sockfd, &readfds);
  if ((select(socket->sockfd + 1, &readfds, NULL, NULL, &timeout)) == 1)
    {
      return TRUE;
    }

  return FALSE;
}


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


gboolean
gnet_udp_socket_has_packet (const GUdpSocket* socket)
{
  gint bytes_received;
  gchar data[1];
  guint packetlength;
  u_long arg;
  gint error;

  arg = 1;
  ioctlsocket(socket->sockfd, FIONBIO, &arg); /* set to non-blocking mode */

  packetlength = 1;
  bytes_received = recvfrom(socket->sockfd, (void*) data, packetlength, 
			    MSG_PEEK, NULL, NULL);

  error = WSAGetLastError();

  arg = 0;
  ioctlsocket(socket->sockfd, FIONBIO, &arg); /* set blocking mode */

  if (bytes_received == SOCKET_ERROR)
    {
      if (WSAEMSGSIZE != error)
	{
	  return FALSE;
	}
      /* else, the buffer was not big enough, which is fine since we
	 just want to see if a packet is there..*/
    }

  if (bytes_received)
    return TRUE;

  return FALSE;
}
	
#endif		/*********** End Windows code ***********/



/**
 *  gnet_udp_socket_get_ttl
 *  @socket: a #GUdpSocket
 *
 *  Gets the time-to-live (TTL) default of a #GUdpSocket.  All UDP
 *  packets have a TTL field.  This field is decremented by a router
 *  before it forwards the packet.  If the TTL reaches zero, the
 *  packet is discarded.  The default value is sufficient for most
 *  applications.
 *
 *  Returns: the TTL (an integer between 0 and 255), -1 if the kernel
 *  default is being used, or an integer less than -1 on error.
 *
 **/
gint
gnet_udp_socket_get_ttl (const GUdpSocket* socket)
{
  int ttl;
  socklen_t ttl_size;
  int rv = -2;

  ttl_size = sizeof(ttl);

  /* Get the IPv4 TTL if it's bound to an IPv4 address */
  if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET)
    {
      rv = getsockopt(socket->sockfd, IPPROTO_IP, IP_TTL, 
		      (void*) &ttl, &ttl_size);
    }

  /* Or, get the IPv6 TTL if it's bound to an IPv6 address */
#ifdef HAVE_IPV6
  else if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
    {
      rv = getsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 
		      (void*) &ttl, &ttl_size);
    }
#endif
  else
    g_assert_not_reached();

  
  if (rv == -1)
    return -2;

  return ttl;
}


/**
 *  gnet_udp_socket_set_ttl
 *  @socket: a #GUdpSocket
 *  @ttl: value to set TTL to
 *
 *  Sets the time-to-live (TTL) default of a #GUdpSocket.  Set the TTL
 *  to -1 to use the kernel default.  The default value is sufficient
 *  for most applications.
 *
 *  Returns: 0 if successful.
 *
 **/
gint
gnet_udp_socket_set_ttl (GUdpSocket* socket, gint ttl)
{
  int rv1, rv2;
#ifdef HAVE_IPV6
  GIPv6Policy policy;
#endif

  rv1 = -1;
  rv2 = -1;

  /* If the bind address is anything IPv4 *or* the bind address is
     0::0 IPv6 and IPv6 policy allows IPv4, set IP_TTL.  In the latter case,
     if we bind to 0::0 and the host is dual-stacked, then all IPv4
     interfaces will be bound to also. */
  if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET 
#ifdef HAVE_IPV6
      || (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6 &&
       IN6_IS_ADDR_UNSPECIFIED(&GNET_SOCKADDR_SA6(socket->sa).sin6_addr) &&
       ((policy = gnet_ipv6_get_policy()) == GIPV6_POLICY_IPV4_THEN_IPV6 ||
	policy == GIPV6_POLICY_IPV6_THEN_IPV4))
#endif
      )
    {
      rv1 = setsockopt(socket->sockfd, IPPROTO_IP, IP_TTL, 
		       (void*) &ttl, sizeof(ttl));
    }


  /* If the bind address is IPv6, set IPV6_UNICAST_HOPS */
#ifdef HAVE_IPV6
  if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6)
    {
      rv2 = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 
		       (void*) &ttl, sizeof(ttl));
    }
#endif

  if (rv1 == -1 && rv2 == -1)
    return -1;

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1