/*=====================================================================*/
/*    serrano/prgm/project/bigloo/runtime/Clib/csocket.c               */
/*    -------------------------------------------------------------    */
/*    Author      :  Manuel Serrano                                    */
/*    Creation    :  Mon Jun 29 18:18:45 1998                          */
/*    Last change :  Fri Nov 14 21:23:30 2003 (serrano)                */
/*    -------------------------------------------------------------    */
/*    Scheme sockets                                                   */
/*    -------------------------------------------------------------    */
/*    This file is based on a contribution of                          */
/*    David Tolpin (dvd@pizza.msk.su)                                  */
/*                                                                     */
/*    Bugs correction (conversion between host and network byte order) */
/*    by Marc Furrer (Marc.Furrer@di.epfl.ch)                          */
/*                                                                     */
/*    Reworked  by Erick Gallesio for 2.2 release.                     */
/*    Some additions and simplifications (I hope).                     */
/*=====================================================================*/
#include <sys/types.h>
#ifndef _MSC_VER
#   include <sys/socket.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#else
#   include <winsock2.h>
#endif
#include <memory.h>
#include <errno.h>
#include <bigloo.h>

#define socklen_t void

#ifndef _MSC_VER
#   define BAD_SOCKET(s) ((s) < 0)
#else
#   define BAD_SOCKET(s) ((s) == INVALID_SOCKET)
#endif


/*---------------------------------------------------------------------*/
/*    Importations ...                                                 */
/*---------------------------------------------------------------------*/
extern long default_io_bufsiz;
extern obj_t close_input_port( obj_t );
extern obj_t close_output_port( obj_t );
extern obj_t file_to_buffered_input_port( FILE *, long );

#ifndef _MSC_VER
  extern int dup( int );
  extern int close( int );
#endif

extern long bgl_nb_fread( char *, long , long , FILE * );
extern int bgl_feof( FILE * );

#if( !defined( feof ) )
#  define  bgl_feof feof
#endif


/*---------------------------------------------------------------------*/
/*    static void                                                      */
/*    socket_error ...                                                 */
/*---------------------------------------------------------------------*/
static void
socket_error( char *who, char *message, obj_t object ) {
   FAILURE( string_to_bstring( who ), string_to_bstring( message ), object );
}

/*---------------------------------------------------------------------*/
/*    static void                                                      */
/*    system_error ...                                                 */
/*---------------------------------------------------------------------*/
static void
system_error( char *who ) {
   char buffer[ 512 ]; 
  
   sprintf( buffer, "%s (%d)", strerror( errno ), errno );
   socket_error( who, buffer, BUNSPEC );
}

/*---------------------------------------------------------------------*/
/*    static void                                                      */
/*    set_socket_io_ports ...                                          */
/*---------------------------------------------------------------------*/
static void
set_socket_io_ports( int s, obj_t sock, char *who, char bufferedp ) {
   int t, len, port;
   obj_t hostname;
   char *fname;
   FILE *fs, *ft = 0;
   char buffer[ 200 ];

   /* duplicate handles so that we are able to access one */
   /* socket channel via two scheme ports.                */
   t = dup( s ); 

   if( t == -1 ) {
      sprintf( buffer, "%s: cannot duplicate io port", who );
      socket_error( "set_socket_io_ports", buffer, BUNSPEC );
   }

   if( !((fs = fdopen(s, "r")) && (ft = fdopen(t, "w"))) ) {
      sprintf( buffer, "%s: cannot create socket io ports", who );
      socket_error( "set_socket_io_ports", buffer, BUNSPEC );
  }

  port     = SOCKET( sock ).portnum;
  hostname = SOCKET( sock ).hostname;
  len      = STRING_LENGTH( hostname ) + 20;
  fname    = BGL_HEAP_DEBUG_MARK_STR( (char *)GC_MALLOC_ATOMIC( len ) );
  sprintf( fname, "%s:%d", BSTRING_TO_STRING( hostname ), port );

  /* Create input port */
  SOCKET( sock ).input = file_to_buffered_input_port( fs, bufferedp ? -1 : 1 );
  SOCKET( sock ).input->input_port_t.kindof  = KINDOF_SOCKET;
  SOCKET( sock ).input->input_port_t.name    = fname;
  SOCKET( sock ).input->input_port_t.sysread = (int (*)())bgl_nb_fread;

  /* Create output port */
  SOCKET( sock ).output = make_output_port( fname, ft, KINDOF_FILE );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    make_client_socket ...                                           */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
make_client_socket( obj_t hostname, int port, char bufferedp ) {
   char str[] = "make-client-socket";
   struct hostent *hp;
   struct sockaddr_in server;
   int s;
   obj_t a_socket;
 
   /* Locate the host IP address */
   if( (hp = gethostbyname( (char *)BSTRING_TO_STRING( hostname ) )) == NULL )
      socket_error( str, "unknown or misspelled host name", hostname );

   /* Get a socket */
   if( BAD_SOCKET( s = (int)socket( AF_INET, SOCK_STREAM, 0 )) )
      socket_error( str, "cannot create socket", BUNSPEC );
  
   /* Setup a connect address */
   memset( &server, 0, sizeof( server ) );
   memcpy( (char*)&server.sin_addr, hp->h_addr, hp->h_length );
   server.sin_family = AF_INET;
   server.sin_port   = htons( port );

   /* Try to connect */
   if( connect( s, (struct sockaddr *)&server, sizeof( server ) ) < 0 ) {
      close( s );
      system_error( str );
   }

   /* Create a new Scheme socket object */
   a_socket                    = GC_MALLOC( SOCKET_SIZE );
   a_socket->socket_t.header   = MAKE_HEADER( SOCKET_TYPE, 0 );
   a_socket->socket_t.portnum  = ntohs( server.sin_port );
   a_socket->socket_t.hostname = string_to_bstring( hp->h_name );
   a_socket->socket_t.hostip   = string_to_bstring( inet_ntoa(server.sin_addr) );
   a_socket->socket_t.fd       = s;
   a_socket->socket_t.input    = BFALSE;
   a_socket->socket_t.output   = BFALSE;
   a_socket->socket_t.stype    = SOCKET_CLIENT;

   set_socket_io_ports( s, a_socket, str, bufferedp );
   return BGL_HEAP_DEBUG_MARK_OBJ( BREF( a_socket ) );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    make_server_socket ...                                           */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
make_server_socket( int port ) {
   char msg[] = "make-server-socket";
   struct sockaddr_in sin;
   int s, portnum, len;
   obj_t a_socket;

   /* Determine port to use */
   portnum = port;
   if( portnum < 0 )
      socket_error( "make-server-socket", "bad port number", BINT( port ) );

   /* Create a socket */
   if( BAD_SOCKET( s = (int)socket(AF_INET, SOCK_STREAM, 0) ) )
      socket_error( "make-server-socket", "Cannot create socket", BUNSPEC );
  
   /* Bind the socket to a name */
   sin.sin_family      = AF_INET;
   sin.sin_port        = htons( portnum );
   sin.sin_addr.s_addr = INADDR_ANY;

   /* OPTIONAL */
   {
      int optval;
      optval = 1;
      
      /* set the reuse flag */
      if( setsockopt( s, SOL_SOCKET, SO_REUSEADDR,
		      (char*)&optval, sizeof( optval ) ) < 0 ) {
	 system_error( msg );
      }
   }  
 
   if( bind( s, (struct sockaddr *)&sin, sizeof( sin ) ) < 0 ) {
      close( s );
      system_error( msg );
   }

   /* Query the socket name, permits to get the true socket number */
   /* if 0 was given                                               */
   len = sizeof( sin );
   if( getsockname( s, (struct sockaddr *)&sin, (socklen_t *)&len ) < 0 ) {
      close( s );
      system_error( msg );
   }

   /* Indicate that we are ready to listen */
   if( listen( s, 5 ) < 0 ) {
      close( s );
      system_error( msg );
   }

   /* Now we can create the socket object */
   a_socket                    = GC_MALLOC( SOCKET_SIZE );
   a_socket->socket_t.header   = MAKE_HEADER( SOCKET_TYPE, 0 );
   a_socket->socket_t.portnum  = ntohs( sin.sin_port );
   a_socket->socket_t.hostname = BFALSE;
   a_socket->socket_t.hostip   = BFALSE;
   a_socket->socket_t.fd       = s;
   a_socket->socket_t.input    = BFALSE;
   a_socket->socket_t.output   = BFALSE;
   a_socket->socket_t.stype    = SOCKET_SERVER;

   return BGL_HEAP_DEBUG_MARK_OBJ( BREF( a_socket ) );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_accept_connection ...                                     */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_accept_connection( obj_t sock, char bufferedp ) {
   char *s;
   char str[] = "socket-accept-connection";
   struct sockaddr_in sin;
   struct hostent *host;
   int len = sizeof( sin );
   int new_s;

   if( BAD_SOCKET( new_s = (int)accept( SOCKET( sock ).fd,
					(struct sockaddr *)&sin,
					(socklen_t *)&len ) ) )
      system_error( str );

   /* Set the client info (if possible its name, otherwise its IP number) */
   host = gethostbyaddr( (char *)&sin.sin_addr, sizeof( sin.sin_addr ), AF_INET );
   s = (char *)inet_ntoa( sin.sin_addr );

   SOCKET( sock ).hostip = string_to_bstring( s );
   SOCKET( sock ).hostname = string_to_bstring( host ? host->h_name : s );
   
   set_socket_io_ports( new_s, sock, str, bufferedp );
   
   return BUNSPEC;
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_local_addr ...                                            */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_local_addr( obj_t sock ) {
   struct sockaddr_in sin;
   int len = sizeof( sin );

   if( SOCKET( sock ).stype == SOCKET_SERVER ) {
      return string_to_bstring( "0.0.0.0" );
   }
   
   if( getsockname( SOCKET( sock ).fd,
		    (struct sockaddr *)&sin,
		    (socklen_t *)&len ) )
      socket_error( "socket-local-address", "cannot get socket name", sock );

   return string_to_bstring( (char *)inet_ntoa( sin.sin_addr ) );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_shutdown ...                                              */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_shutdown( obj_t sock, int close_socket ) {
   int fd = SOCKET( sock ).fd;

   if( fd > 0 ) {
      if( close_socket ) {
	 shutdown( fd, 2 );
	 SOCKET( sock ).fd = -1;
      } else {
	 close( fd );
      }
   }
 
   /* Warning: input and output can have already be garbaged :if the   */
   /* socket is no more used, the input and output are not marked as   */
   /* used and can (eventually) be released before the call to         */
   /* shutdown (through free_socket) be done. One way could be to just */
   /* set SOCKET(sock).{in|out}put to #t and wait that next GC frees   */
   /* the ports if not already down. However, this will really         */
   /* disconnect the peer when the GC occurs rather than when the call */
   /* to shutdown is done. This is not important if this function is   */
   /* called by the GC, but could be annoying when it is called by the */
   /* user                                                             */
   if( INPUT_PORTP( SOCKET(sock).input ) ) {
      close_input_port( SOCKET( sock ).input );
      SOCKET( sock ).input = BFALSE;
   }
   if( OUTPUT_PORTP( SOCKET(sock).output ) ) {
      close_output_port( SOCKET( sock ).output );
      SOCKET( sock ).output = BFALSE;
   }
   
   return BUNSPEC;
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_dup ...                                                   */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_dup( obj_t socket ) {
  obj_t a_socket;
  int new_fd;
  
  if( (new_fd = dup( SOCKET(socket).fd )) < 0 )
     socket_error("socket-dup", "cannot duplicate socket", socket );

  a_socket                  = GC_MALLOC( SOCKET_SIZE );
  a_socket->socket_t.header = MAKE_HEADER( SOCKET_TYPE, 0 );

  SOCKET( a_socket ) = SOCKET( socket );
  SOCKET( a_socket ).fd = new_fd;  

  return BGL_HEAP_DEBUG_MARK_OBJ( BREF( a_socket ) );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_accept ...                                                */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_accept( obj_t serv, char bufferedp, int errp ) {
   char str[] = "socket-accept";
   char *s;
   struct sockaddr_in sin;
   struct hostent *host;
   int len = sizeof( sin );
   int new_s;
   obj_t a_socket;
   int new_fd; 
  
   if( (new_fd = dup( SOCKET( serv ).fd )) < 0 ) {
      if( errp )
	 socket_error( "socket-accept",
		       "cannot duplicate file descriptor",
		       serv );
      else
	 return BFALSE;
   }

   if( BAD_SOCKET( new_s = (int)accept( new_fd,
					(struct sockaddr *)&sin,
					(socklen_t *)&len ) ) ) {
      if( errp )
	 system_error( str );
      else
	 return BFALSE;
   }

   /* Set the client info (if possible its name, otherwise its IP number) */
   host = gethostbyaddr( (char *)&sin.sin_addr, sizeof( sin.sin_addr ), AF_INET );
   s = (char *)inet_ntoa( sin.sin_addr );

   /* allocate and fill the new socket client for this connection */
   a_socket = GC_MALLOC( SOCKET_SIZE );
   a_socket->socket_t.header = MAKE_HEADER( SOCKET_TYPE, 0 );
   a_socket->socket_t.portnum = ntohs( sin.sin_port );
   a_socket->socket_t.hostname = string_to_bstring( host ? host->h_name : s );
   a_socket->socket_t.hostip = string_to_bstring( s );
   a_socket->socket_t.fd = new_fd;
   a_socket->socket_t.stype = SOCKET_CLIENT;
   
   set_socket_io_ports( new_s, BREF( a_socket ), str, bufferedp );
   
   return BGL_HEAP_DEBUG_MARK_OBJ( BREF( a_socket ) );
}
   
/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    socket_close ...                                                 */
/*---------------------------------------------------------------------*/
BGL_RUNTIME_DEF
obj_t
socket_close( obj_t sock ) {
   int fd = SOCKET( sock ).fd;
   
   if( fd > 0 ) {
      close( fd );
      SOCKET( sock ).fd = -1;
   }

   if( INPUT_PORTP( SOCKET(sock).input ) ) {
      close_input_port( SOCKET( sock ).input );
      SOCKET( sock ).input = BFALSE;
   }
   if( OUTPUT_PORTP( SOCKET(sock).output ) ) {
      close_output_port( SOCKET( sock ).output );
      SOCKET( sock ).output = BFALSE;
   }
   
   return BUNSPEC;
}

