/* CSL - Common Sound Layer
 * Copyright (C) 1997, 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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	"artssocket.h"

#include	<unistd.h>
#include	<stdlib.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<string.h>
#include	<stdio.h>
#include	<netdb.h>
#include	<sys/time.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<netinet/tcp.h>
#include        <sys/un.h>

/* send/recv OOB buffer maximum sizes
 * AppleTalk DDP	587
 * IPV4			0xffff - 40
 * NetWare SPX		534
 * 
 */
#define	MAX_OOBSIZE		(512)
#define HOSTENT_IS_VALID(he)            ((he) && (he)->h_name && (he)->h_addrtype == AF_INET && \
                                         (he)->h_length == sizeof (struct in_addr) && \
                                         (he)->h_addr_list)
#define HOSTENT_IN_ADDR(hostent_p, n)   ((struct in_addr*) ((hostent_p)->h_addr_list[(n)]))
#define	RETURN_AND_CLOSE(fd)		{ close (fd); return NULL; }


/* --- functions --- */
static inline void
fd_block (int fd,
	  int can_block)
{
  long d_long;
  
  d_long = fcntl (fd, F_GETFL);
  if (can_block)
    d_long &= ~O_NONBLOCK;
  else
    d_long |= O_NONBLOCK;
  if (d_long < 0 || fcntl (fd, F_SETFL, d_long))
    {
      /* why would we ever fail here? */
      abort ();
    }
}

ArtsSocket*
_arts_socket_new_tcp (const char *host_spec)
{
  struct sockaddr_in inet_addr = { 0, };
  struct hostent *hostent;
  char *port_number, *host_name;
  int ipv4_addr, port, l, fd, optval;
  ArtsSocket *csock;
  
  if (!host_spec)
    return NULL;
  
  /* decompose hostname and port */
  port_number = strchr (host_spec, ':');
  if (port_number == NULL)
    return NULL;
  l = port_number - host_spec;
  if (l < 1)
    return NULL;
  port = atoi (port_number + 1);
  if (port < 1)
    return NULL;
  host_name = csl_malloc (l + 1);
  strncpy (host_name, host_spec, l);
  host_name[l] = 0;
  hostent = gethostbyname2 (host_name, AF_INET);
  csl_free (host_name);
  if (!HOSTENT_IS_VALID (hostent))
    return NULL;
  ipv4_addr = ntohl (HOSTENT_IN_ADDR (hostent, 0)->s_addr);
  
  /* open socket on ipv4_addr and port */
  fd = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (fd < 0)
    return NULL;
  optval = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval)) < 0)
    RETURN_AND_CLOSE (fd);
  optval = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_OOBINLINE, &optval, sizeof (optval)) < 0)
    RETURN_AND_CLOSE (fd);

  /* TCP_NODELAY makes MCOP more responsive over TCP connections, by telling
   * TCP to send data we write immediately. We can live without it though,
   * so we don't check the result.
   */
  optval = 1;
  setsockopt (fd, SOL_TCP, TCP_NODELAY, &optval, sizeof (optval));
  
  /* connect socket:
   * struct sockaddr_in {
   *   sa_family_t	sin_family;	// address family: AF_INET
   *   u_int16_t	sin_port;	// port in network byte order
   *  struct in_addr	sin_addr;	// internet address
   * };
   * struct in_addr {			// Internet address
   *   u_int32_t	s_addr;		// address in network byte order
   * };
   */
  inet_addr.sin_family = AF_INET;
  inet_addr.sin_port = htons (port);
  inet_addr.sin_addr.s_addr = htonl (ipv4_addr);
  fd_block (fd, TRUE);
  if (connect (fd, (const struct sockaddr*) &inet_addr, sizeof (inet_addr)) < 0)
    RETURN_AND_CLOSE (fd);
  
  /* success, setup socket structure and outa here */
  csock = csl_malloc (sizeof (*csock));
  csock->fd = fd;
  csock->will_block = TRUE;
  csock->msg_length_offset = -1;
  csock->rbuffer_size = 0;
  csock->rbuffer = NULL;
  csock->n_bytes_read = 0;
  
  return csock;
}

ArtsSocket*
_arts_socket_new_unix (const char *path)
{
  struct sockaddr_un unix_addr = { 0, };
  int path_maxlen;
  int fd, optval;
  ArtsSocket *csock;
  
  if (!path)
    return NULL;
  
  /* open unix domain socket */
  fd = socket (PF_UNIX, SOCK_STREAM, 0);
  if (fd < 0)
    return NULL;
  optval = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval)) < 0)
    RETURN_AND_CLOSE (fd);
  optval = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_OOBINLINE, &optval, sizeof (optval)) < 0)
    RETURN_AND_CLOSE (fd);
  
  /* connect socket: */
  path_maxlen = sizeof (unix_addr.sun_path);
  strncpy (unix_addr.sun_path, path, path_maxlen);
  unix_addr.sun_path[path_maxlen-1] = 0;
  unix_addr.sun_family = AF_UNIX;
  
  fd_block (fd, TRUE);
  if (connect (fd, (const struct sockaddr*) &unix_addr, sizeof (unix_addr)) < 0)
    RETURN_AND_CLOSE (fd);
  
  /* success, setup socket structure and outa here */
  csock = csl_malloc (sizeof (*csock));
  csock->fd = fd;
  csock->will_block = TRUE;
  csock->msg_length_offset = -1;
  csock->rbuffer_size = 0;
  csock->rbuffer = NULL;
  csock->n_bytes_read = 0;
  
  return csock;
}


void
_arts_socket_may_block (ArtsSocket *csock,
			int        may_block)
{
  csl_return_if_fail (csock != NULL);
  
  may_block = may_block != FALSE;
  if (may_block != csock->will_block)
    {
      fd_block (csock->fd, may_block);
      csock->will_block = may_block;
    }
}

static void
socket_debug_buffer (const char  *prefix,
		     unsigned int n_bytes,
		     void        *bytes)
{
  char *buffer = bytes;
  
  if (n_bytes && FALSE)
    {
      int i;
      
      if (prefix)
	fprintf (stderr, "DEBUG: %s\n", prefix);
      for (i = 0; i < n_bytes; )
	{
	  unsigned int j, n, d[16] = { 0, };
	  
	  for (n = 0; n < 16; n++)
	    if (i < n_bytes)
	      d[n] = buffer[i++];
	    else
	      break;
	  fprintf (stderr, " ");
	  for (j = 0; j < n; j++)
	    fprintf (stderr, "%s %02x", j == 8 ? "  " : "", d[j] & 0xff);
	  for (; j < 16; j++)
	    fprintf (stderr, "%s   ", j == 8 ? "  " : "");
	  fprintf (stderr, "    ");
	  for (j = 0; j < n; j++)
	    fprintf (stderr, "%c", d[j] >= 32 ? d[j] & 0xff : '.');
	  fprintf (stderr, "\n");
	}
    }
}

void
_arts_socket_write (ArtsSocket   *csock,
		    unsigned int n_bytes,
		    void        *bytes)
{
  csl_return_if_fail (csock != NULL);
  csl_return_if_fail (n_bytes >= 0);
  
  if (n_bytes)
    {
      unsigned int n_bytes_written = 0;
      char *debug_buffer = bytes;
      
      csl_return_if_fail (bytes != NULL);
      
      do
	{
	  int l;
	  
	  errno = 0;
	  l = write (csock->fd, bytes, n_bytes);
	  
	  if (l > 0)
	    {
	      bytes += l;
	      n_bytes -= l;
	      n_bytes_written += l;
	    }
	}
      while (n_bytes && errno == EINTR);
      
      socket_debug_buffer ("socket write:", n_bytes_written, debug_buffer);
    }
}

unsigned int
_arts_socket_read (ArtsSocket   *csock,
		   unsigned int n_bytes,
		   void        *bytes)
{
  int n_bytes_read = 0;
  char *debug_buffer = bytes;
  
  csl_return_val_if_fail (csock != NULL, 0);
  
  if (n_bytes)
    {
      csl_return_val_if_fail (bytes != NULL, 0);
      
      do
	{
	  int l;
	  
	  errno = 0;
	  l = read (csock->fd, bytes, n_bytes);
	  
	  if (l > 0)
	    {
	      bytes += l;
	      n_bytes -= l;
	      n_bytes_read += l;
	    }
	}
      while (n_bytes && errno == EINTR);
      
      socket_debug_buffer ("socket read:", n_bytes_read, debug_buffer);
    }
  
  return n_bytes_read;
}

static inline unsigned int
csock_msg_length (ArtsSocket *csock)
{
  if (csock->msg_length_offset >= 0 &&
      csock->n_bytes_read >= csock->msg_length_offset + 4)
    {
      void *p = csock->rbuffer + csock->msg_length_offset;
      unsigned int *l = p;
      
      return ntohl (*l);
    }
  return 0;
}

void*
_arts_socket_peek_msg (ArtsSocket    *csock,
		       unsigned int *n_bytes_p)
{
  unsigned int l, default_buffer_size = 1024;
  
  csl_return_val_if_fail (csock != NULL, NULL);
  csl_return_val_if_fail (n_bytes_p != NULL, NULL);
  
  l = csock_msg_length (csock);
  if (csock->n_bytes_read < l || (!l && csock->n_bytes_read < default_buffer_size))
    {
      if (!l)
	l = default_buffer_size;
      l -= csock->n_bytes_read;
      if (csock->rbuffer_size < l + csock->n_bytes_read)
	{
	  csock->rbuffer = csl_realloc (csock->rbuffer, l + csock->n_bytes_read);
	  csock->rbuffer_size = l + csock->n_bytes_read;
	}
      csock->n_bytes_read += _arts_socket_read (csock, l, csock->rbuffer + csock->n_bytes_read);
      
      l = csock_msg_length (csock);
    }
  
  if (l && csock->n_bytes_read >= l)
    {
      *n_bytes_p = l;
      return csock->rbuffer;
    }
  else
    *n_bytes_p = 0;
  return NULL;
}

void
_arts_socket_skip_msg (ArtsSocket   *csock,
		       unsigned int n_bytes)
{
  unsigned int l;
  
  csl_return_if_fail (csock != NULL);
  csl_return_if_fail (n_bytes > 0);
  
  l = csock_msg_length (csock);
  
  /* skip message only if sizes match */
  csl_return_if_fail (l > 0 && l == n_bytes && csock->n_bytes_read >= l);
  
  memmove (csock->rbuffer, csock->rbuffer + l, csock->n_bytes_read - l);
  csock->n_bytes_read -= l;
}

CslBool
_arts_socket_wait_for_msg (ArtsSocket   *csock,
			   int	     *milliseconds_p)
{
  struct timeval tv_before_select, tv_after_select;
  unsigned int l, n_bytes;
  int should_block;
  int milliseconds = -1;

  csl_return_val_if_fail (csock != NULL, FALSE);
  csl_return_val_if_fail (csock->msg_length_offset >= 0, FALSE);

  /* input:  *milliseconds_p (NULL==-1)
   *   >0	- milliseconds to wait for at maximum
   *  ==0	- just check for completed messages
   *   <0	- wait inifinitely
   * output: *milliseconds_p == input milliseconds - milliseconds spent (or still -1)
   */
  milliseconds = milliseconds_p ? *milliseconds_p : -1;
  
  l = csock_msg_length (csock);
  if (l && csock->n_bytes_read >= l)
    return TRUE;

  should_block = csock->will_block;
  _arts_socket_may_block (csock, FALSE);
  
  _arts_socket_peek_msg (csock, &n_bytes);
  l = csock_msg_length (csock);
  while ((!l || l > csock->n_bytes_read) && milliseconds != 0)
    {
      struct timeval timeout;
      unsigned int n_bytes;
      fd_set readfds;
      
      if (milliseconds > 0)
	{
	  timeout.tv_sec = (milliseconds * 1000) / 1000000;
	  timeout.tv_usec = (milliseconds * 1000) % 1000000;
	}
      else
	{
	  timeout.tv_sec = 5000000;
	  timeout.tv_usec = 0;
	}
      
      FD_ZERO (&readfds);
      FD_SET (csock->fd, &readfds);
      
      gettimeofday (&tv_before_select, 0);
      select (csock->fd + 1, &readfds, 0, 0, &timeout);
      gettimeofday (&tv_after_select, 0);
      
      if (milliseconds > 0)
	{
	  milliseconds -= ((tv_after_select.tv_sec - tv_before_select.tv_sec) * 1000 +
			   (tv_after_select.tv_usec - tv_before_select.tv_usec) / 1000);
	  if (milliseconds < 0)
	    milliseconds = 0;
	}
      
      if (FD_ISSET (csock->fd, &readfds))
	{
	  _arts_socket_peek_msg (csock, &n_bytes);
	  l = csock_msg_length (csock);
	}
    }

  _arts_socket_may_block (csock, should_block);
  
  if (milliseconds_p)
    *milliseconds_p = milliseconds;

  return l && l <= csock->n_bytes_read;
}

void
_arts_socket_free (ArtsSocket *csock)
{
  csl_return_if_fail (csock != NULL);
  
  close (csock->fd);
  csl_free (csock->rbuffer);
  csl_free (csock);
}
