/*
 * sr.c,v 1.2 1994/01/28 18:45:00 franktor Exp
 *
 * sr.c
 *
 * Copyright (c) 1992 Nordic SR-NET
 * Geir Pedersen, Geir.Pedersen@usit.uio.no
 *
 * Implementation of the low level SR-API for ISODE.
 *
 *
*/

/*
 * TODO
 *
 * o make sure that the proper element is removed from the "incomming" queue.
 *
*/


#include <sys/types.h>
/*#include <isode/tsap.h>*/
#include "SR.h"
#include "ISO10163-SR-1-types.h"
#include "tlv.h"
#include <sr-api.h>
#include <sr-general.h>
#include <sr-util.h>
#include <sr-low.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "tcpip.h"
#include <isode/psap.h>

typedef struct qElem {		/* holding an incomming request */
   int		       	ref;
   PE		   	SR_APDU;
   PE			oprPE;
   SROperation		opr;
   struct qElem		*next;
} qElem;

typedef struct fileDesc {	/* holding information on each open file descriptor towards the net */
   int			fd;
   PS			ps;
   struct fileDesc     	*next;
} fileDesc;

typedef struct writeBuf {
   int			fd;
   int			len;
   char			*buf;
   struct writeBuf	*next;
} writeBuf;

/* extern char *strerror ( void ); */
#ifdef __ultrix__
#define strerror() strerror(errno)
#endif

#ifndef __CEXTRACT
#include "encdecproto.h"
#include "srproto.h"
#endif

/*
 * Utility functions
 *
*/

#if 0
static Boolean FD_ISZERO ( fd_set *x )
{
   int			i;
   int			size = sizeof ( x->fds_bits ) / sizeof ( *x->fds_bits );

   if ( !x )
      return True;
   
   for ( i = 0; i < size; i++ )
      if ( x->fds_bits[i] )
	 return False;

   return True;
}

/* Moved to cli-util.	-hbf */
octetString *osalloc ( void )
octetString *str2os ( const char *str );
char *os2str ( octetString *oc );
octetString *osdup ( octetString *oc );
#endif


/*
 * Procedural interface to the tcp/ip ZIT based module
 *
*/

extern int     		ps_len_strategy; /* from ISODE */
static qElem		*incomming		= (qElem *) NULL; /* queue of incomming unprocessed PEs */
fd_set			allFileDescriptors;
fd_set			writeblockedFileDescriptors;
fileDesc		*netFiles;
Boolean			hasListener		= False;
int			listenerSocket;
Boolean 		(*AcceptFunc)(int, SR_protocolVersion, LowerLayerKind, presContext *);
void			(*AbortFunc)(int);
char			lastError[1000];      
writeBuf		*writeBuffers; /* data to be written when FD gets unblocked */


/*
 * OSI stuff 
*/

Boolean _TCPIP_Initialise ( LogLevel logLevel, FILE *log, void (*Abort)(int) )
{
   AbortFunc = Abort;

   FD_ZERO(&allFileDescriptors);
   FD_ZERO(&writeblockedFileDescriptors);
   hasListener = False;
   netFiles = (fileDesc *) NULL;
   writeBuffers = (writeBuf *) NULL;

   ps_len_strategy = PS_LEN_LONG; /* never use indefenite length in presentation streams */
   
   return True;
}

Boolean _TCPIP_Verify ( void )
{
   return True;
}

void _TCPIP_Close ( void )
{
}

void _TCPIP_GetSelectFD_SET ( fd_set **rs, fd_set **ws )
{
   *rs = &allFileDescriptors;
   *ws = &writeblockedFileDescriptors;
}

char *_TCPIP_LastError ( void )
{
   return lastError;
}

void TCPIP_SetError ( char *err )
{
   strncpy ( lastError, err, 1000 );
}

int pe2str ( PE pe, char **buf, int *len )
{
   static PS 			ps	= (PS) NULL;

   if ( !ps )
   {
      ps = ps_alloc ( str_open );
      str_setup ( ps, NULLCP, 500, 0 );
   }
   else
   {
      ps->ps_ptr = ps->ps_base;
      ps->ps_cnt = ps->ps_bufsiz;
   }

   if ( pe2ps ( ps, pe ) == NOTOK )
   {
      LOG ( facLow, llevExceptions, "Failed to convert presentation element to character string: %s", ps_error ( pe->pe_errno ) );

      return NOTOK;
   }

   *buf = ps->ps_base;
   *len = ps->ps_ptr - ps->ps_base;

   return OK;
}

static Boolean SendPE ( int ref, PE pe, char *operation, char *kind )
{
   static PS			ps	= (PS) NULL;
   static FILE			*f;
   char				*buf;
   int				len;
   int				res;

   if ( !ps )
   {
      ps = ps_alloc ( std_open );
      
      if ( (f = fopen ( "PE-send.log", "w+" )) )
      {
	 fprintf ( f, "\n\n====================\n" );
	 fflush ( f );
      }

      /* I do not remember if the stream may be NULL */
      (void) std_setup ( ps, (f ? f : fopen ( "/dev/null", "w+" )) );
   }
   pe2pl ( ps, pe );
   if (f)
      fflush ( f );

   if ( pe2str ( pe, &buf, &len ) == NOTOK )
   {
      LOG ( facLow, llevExceptions, "SendPE() failed to serialise PE" );

      return False;
   }

   if ( (res = write ( ref, buf, len )) != len )
   {
      writeBuf			*b;
      
      if ( res == -1 )
      {
	 /* hard failure */
	 LOG ( facLow, llevExceptions, "Failed to write PDU onto the TCP/IP network: %s",
	       strerror ( ) );
	 TCPIP_SetError ( strerror ( ) );
	 return False;
      }

      /* save whatever could not be written now */
      b = (writeBuf *) malloc ( sizeof ( struct writeBuf ) );
      b->fd = ref;
      b->len = len - res;
      b->buf = malloc ( b->len );
      bcopy ( buf+res, b->buf, b->len );

      b->next = writeBuffers;
      writeBuffers = b;

      FD_SET ( ref, &writeblockedFileDescriptors );
   }

   return True;
}


/*
 * INTERNAL ROUTINES
 *
*/

static void TCPIP_InternalAbort ( int ref )
{
   qElem			*e, *x, *p;

   LOG ( facLow, llevExceptions, "Aborting association %d", ref );

   close ( ref );		/* ABORT */
   (*AbortFunc)(ref);

   /* pass abort indication to user */
   e = (qElem *) smalloc ( sizeof ( qElem ) );
   e->ref = ref;
   e->SR_APDU = NULLPE;
   e->oprPE = NULLPE;
   e->opr = oprAbort;

   for ( x = incomming, p = (qElem *) NULL; x; p = x, x = x->next );
   if ( !p )
      incomming = e;
   else
      p->next = e;
   e->next = (qElem *) NULL;
}

static Boolean TCPIP_init ( void )
{
   presContext                  *proposedPresContext    = (presContext *) NULL;
   presContext                  *pc;
   int				sock;
   struct sockaddr_in		remaddr;
   int				addrlen;
   fileDesc			*fd;
   
   if ( (sock = accept ( listenerSocket, &remaddr, &addrlen )) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to accept an association: %s", strerror() );
      
      return False;
   }

   LOG ( facLow, llevTrace, "received a connection from %s+%d ( %d )",
	 inet_ntoa ( remaddr.sin_addr ), remaddr.sin_port, sock );

   /* generate presentation context description to be handed over to user for inspection */
   pc = newPC ( Oid_SR_ASN1, Oid_SR_BER, 1 );

   if ( (*AcceptFunc)( sock, proto_SR_V2, llkTCPIP, proposedPresContext ) )
   {
      /* Accepted */

      /* Update presentation context */
   }
   else
   {
      /* Rejected */
      LOG ( facLow, llevExceptions, "User rejected connection" );
      close ( sock );
      
      return False;
   }

   FD_SET ( sock, &allFileDescriptors );

   tlv_open ( sock );

   fd = (fileDesc *) malloc ( sizeof ( fileDesc ) );
   fd->ps = ps_alloc ( fdx_open );
   fdx_setup ( fd->ps, sock );
   fd->fd = sock;

   fd->next = netFiles;
   netFiles = fd;

   return True;
}

static Boolean TCPIP_work ( remoteRef )
      int			remoteRef;
{
   int				i;
   char				*cPDU;
   qElem			*e;
   qElem			*x, *p;
   int				PDUlen;
   struct _SR_APDU		*sr_apdu;
   static PS			ps	= (PS) NULL;
   static FILE 			*f;
   
   LOG ( facLow, llevTrace, "TCPIP_work ( %d )", remoteRef );

   cPDU = tlv_parse ( remoteRef, &PDUlen );

   if ( tlv_error ( remoteRef ) )
   {
      /* A failure occured on the TLV-level - terminate the connection */
      LOG ( facLow, llevExceptions, "TLV-parsing failed - closing connection" );

      TCPIP_InternalAbort ( remoteRef );
   }

   if ( !cPDU )
      return OK;		/* No PDU ready yet */

   e = (qElem *) smalloc ( sizeof ( qElem ) );
   e->SR_APDU = ssdu2pe ( cPDU, PDUlen, NULLIP, &i );
   if ( i )
   {
      LOG ( facLow, llevExceptions,
	    "Failed to convert memory buffer to PE: %s", ps_error ( i ) );
      free ( e );
      TCPIP_InternalAbort ( remoteRef );
      return OK;
   }
   e->ref = remoteRef;
   e->oprPE = NULLPE;
   e->opr = oprNone;
      
   if ( !ps )
   {
      ps = ps_alloc ( std_open );
      
      if ( (f = fopen ( "PE-recv.log", "w+" )) )
      {
	 fprintf ( f, "\n\n====================\n" );
	 fflush ( f );
      }

      /* I do not remember if the stream may be NULL */
      (void) std_setup ( ps, (f ? f : fopen ( "/dev/null", "w+" )) );
   }
   
   pe2pl ( ps, e->SR_APDU );
   if (f)
      fflush ( f );
	 
   if ( decode_ISO10163__SR__1_SR__APDU ( e->SR_APDU, 1, 0, NULLCP, &sr_apdu ) == NOTOK )
   {
      LOG ( facLow, llevExceptions, "_SR_Work() failed to decode SR-APDU" );

      pe_free ( e->SR_APDU );
      free ( e );

      TCPIP_InternalAbort ( remoteRef );
      return OK;
   }

   /* Hack to get around stripped SEQUENCE */
   if ( e->SR_APDU->pe_un1.un_pe_cons->pe_id != PE_CONS_SEQ )
   {
      PE                seq;
      PE                p1, p2;

      seq = pe_alloc ( PE_CLASS_UNIV, PE_FORM_CONS, PE_CONS_SEQ );
      p1 = sr_apdu->pe, p2 = p1->pe_next, p1->pe_next = NULLPE;
      do {
	 p1 = p2;
	 p2 = p2->pe_next;
	 if ( p1 )
	    p1->pe_next = NULLPE;

	 seq_add ( seq, p1, -1 );
      }
      while ( p2 );

      sr_apdu->pe = seq;
   }

   switch ( sr_apdu->offset )
   {
    case 1: e->opr = oprInitialiseRequest;       break;
    case 2: e->opr = oprInitialiseResponse;      break;
    case 3: e->opr = oprSearchRequest;           break;
    case 4: e->opr = oprSearchResponse;          break;
    case 5: e->opr = oprPresentRequest;          break;
    case 6: e->opr = oprPresentResponse;         break;
    case 7: e->opr = oprDeleteResultSetRequest;  break;
    case 8: e->opr = oprDeleteResultSetResponse; break;
    default:
      LOG ( facLow, llevExceptions, "_SR_work(): unknown operation" );
      TCPIP_InternalAbort ( remoteRef );
      return OK;
   }
   
   e->oprPE = sr_apdu->pe;
	 
   /* append the new APDU to the queue of incomming APDUs */
   for ( x = incomming, p = (qElem *) NULL; x; p = x, x = x->next );
   if ( !p )
      incomming = e;
   else
      p->next = e;
   e->next = (qElem *) NULL;
   
   return OK;
}



/*
 * OPEN AND CLOSE
*/

Boolean _TCPIP_OpenAssociation ( SR_Address *to, presContext **pci, int *remoteRef )
{
   presContext			*pc			= (presContext *) NULL;
   struct sockaddr_in		remaddr;
   int				sock;
   fileDesc			*fd;

   if ( !to || to->addressType != addrTCPIP )
   {
      LOG ( facLow, llevExceptions, "Unspported addresstype for TCPIP" );
      return False;
   }
   
   remaddr.sin_family = AF_INET;
   remaddr.sin_port = ZIT_PORT;

   if ( to->addr.tcpip_address && ((remaddr.sin_addr.s_addr = inet_addr ( to->addr.tcpip_address ) == -1)) )
   {
      struct hostent		*hent = gethostbyname ( to->addr.tcpip_address );

      if ( !hent )
      {
	 LOG ( facLow, llevExceptions, "Failed to resolve %s into an IP-address\n", to->addr.tcpip_address );

	 return False;
      }
      bcopy ( *hent->h_addr_list, &remaddr.sin_addr.s_addr, hent->h_length );
   }
   
   if ( (sock = socket ( PF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to create a new socket: %s", strerror() );

      return False;
   }

   if ( connect ( sock, (struct sockaddr *) &remaddr, sizeof ( struct sockaddr_in ) ) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to connect to remote server: %s", strerror() );
      close ( sock );

      return False;
   }
      
   *remoteRef = sock;
   FD_SET ( sock, &allFileDescriptors );

   tlv_open ( sock );

   fd = (fileDesc *) malloc ( sizeof ( fileDesc ) );
   fd->ps = ps_alloc ( fdx_open );
   fdx_setup ( fd->ps, sock );
   fd->fd = sock;

   fd->next = netFiles;
   netFiles = fd;

   /* Accept all presentation contexts */
   if ( pci )
      for ( pc = *pci; pc; pc = pc->next )
	 pc->accepted = True;

   return True;
}

Boolean _TCPIP_CreateListener ( SR_Address *where, Boolean (*accept)( int, SR_protocolVersion, LowerLayerKind, presContext *pci ) )
{
   struct sockaddr_in		locaddr;
   int				sock;
   unsigned int			i;

   if ( !where || where->addressType != addrTCPIP )
   {
      LOG ( facLow, llevExceptions, "Unspported addresstype for TCPIP" );
      return False;
   }
   
   AcceptFunc = accept;

   bzero ( &locaddr, sizeof ( struct sockaddr_in ) );
   locaddr.sin_family = AF_INET;
   locaddr.sin_port = ZIT_PORT;

   if ( where->addr.tcpip_address && ((locaddr.sin_addr.s_addr = inet_addr ( where->addr.tcpip_address )) == -1) )
   {
      struct hostent		*hent = gethostbyname ( where->addr.tcpip_address );

      if ( !hent )
      {
	 LOG ( facLow, llevExceptions, "Failed to resolve %s into an IP-address\n", where->addr.tcpip_address );

	 return False;
      }
      bcopy ( *hent->h_addr_list, &locaddr.sin_addr.s_addr, hent->h_length );
   }

   if ( (sock = socket ( PF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to create a new socket: %s", strerror() );

      return False;
   }

   if ( bind ( sock, (struct sockaddr *) &locaddr, sizeof ( struct sockaddr_in ) ) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to bind to socket with address %s+%d: %s",
	    inet_ntoa ( locaddr.sin_addr ), locaddr.sin_port, strerror() );
      close ( sock );
      
      return False;
   }

   i = 1;
   (void) setsockopt ( sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof ( i ) );

   if ( listen ( sock, 5 ) == -1 )
   {
      LOG ( facLow, llevExceptions, "Failed to listen to socket with address %s+%d: %s",
	    inet_ntoa ( locaddr.sin_addr ), locaddr.sin_port, strerror() );
      close ( sock );
      
      return False;
   }      
   
   hasListener = True;
   listenerSocket = sock;
   FD_SET ( sock, &allFileDescriptors );

   return True;
}

void _TCPIP_AbortAssociation ( int ref )
{
   fileDesc			*ths, *prev;
   
   if ( hasListener && ref == listenerSocket )
      hasListener = False;

   for ( ths = netFiles, prev = (fileDesc *) NULL;
	 ths && ths->fd != ref;
	 prev = ths, ths = ths->next );
   if ( ths )
   {
      ps_free ( ths->ps );
      if ( prev )
	 prev->next = ths->next;
      else
	 netFiles = ths->next;
      free ( ths );
   }

   FD_CLR ( ref, &allFileDescriptors );
   FD_CLR ( ref, &writeblockedFileDescriptors );
   tlv_close ( ref );

   close ( ref );
}


Boolean _TCPIP_VerifyAssociation ( int ref )
{
   return False;
}

void _TCPIP_ClearAssociation ( int ref )
{
   qElem			*x, *p;

   for ( x = incomming, p = (qElem *) NULL; x; p = x, x = x->next )
      if ( x->ref == ref )
      {
	 if ( p )
	    p->next = x->next;
	 else
	    incomming = x->next;

	 if ( x->SR_APDU )
	    pe_free ( x->SR_APDU );
	 free ( x );
      }
}


Boolean _TCPIP_CheckIncommingQ ( int *ref, qElem **qelem )
{
   qElem			*x, *prev;

   for ( x = incomming, prev = (qElem *) NULL; x; prev = x, x = x->next )
      if ( (*ref == -1) || (*ref == x->ref) )
      {
	 *ref = x->ref;
	 *qelem = x;

	 /* move found element first in queue */
	 if ( prev )
	 {
	    prev->next = x->next;
	    x->next = incomming;
	    incomming = x;
	 }
	 
	 return True;
      }

   return False;
}


/*
 * Handle file  descriptors with activity reported in the masks
 * 
 */

void _TCPIP_HandleFileDescriptors ( fd_set *rfds, fd_set *wfds, fd_set *efds )
{
   int			 	i;

   for ( i = 0; i < FD_SETSIZE; i++ )
   {
      if ( FD_ISSET(i, &writeblockedFileDescriptors) && FD_ISSET(i, wfds) )
      {
	 /* found a file descriptor ready to be written on */
	 writeBuf		*b, *pb;
	 Boolean		found;

	 for ( found = False, pb = (writeBuf *) NULL, b = writeBuffers; b; b = b->next )
	    if ( b->fd == i )
	    {
	       int		res;
	       
	       if ( (res = write ( b->fd, b->buf, b->len )) != b->len )
	       {
		  if ( res == -1 )
		  {
		     /* hard failure */
		     LOG ( facLow, llevExceptions, "Failed to write PDU onto the TCP/IP network: %s",
			   strerror ( ) );
		     TCPIP_SetError ( strerror ( ) );
		     TCPIP_InternalAbort ( b->fd );
		  }
		  else
		  {
		     b->len -= res;
		     bcopy ( b->buf+res, b->buf, b->len );
		     continue;	/* not all was written */
		  }
	       }

	       /* finished with this buffer - mark it for now */
	       free ( b->buf );
	       b->buf = NULLCP;
	    }

	 /* free all finished buffers */
	 for ( b = writeBuffers, pb = (writeBuf *) NULL; b; pb = b, b = b->next )
	  again:
	    if ( !b->buf )
	    {
	       free ( b );
	       if ( pb )
	       {
		  pb->next = b->next;
		  b = pb;
	       }
	       else
	       {
		  b = writeBuffers = b->next;
		  goto again;
	       }
	    }
      }
      if ( FD_ISSET(i, &allFileDescriptors) && FD_ISSET(i, rfds) )
      {
	 /* found a file descriptor ready to be read */
	 if ( hasListener && i == listenerSocket )
	    /* someone is trying to establish a connection to us */
	    (void) TCPIP_init ();
	 else
	    /* data is available on a connection */
	    (void) TCPIP_work ( i );
      }
   }
}



/*
 * Call this function to poll for operations received over the network
 * Will return oprNone if there are no more queued operations 
 *
*/

SROperation _TCPIP_PollNetwork ( struct timeval *timeout, int *ref )
{
   qElem			*qe;
   fd_set			ifds;

   if ( _TCPIP_CheckIncommingQ ( ref, &qe ) )
   {
      SROperation		opr 		= qe->opr;
      
      *ref = qe->ref;

      if ( qe->opr == oprAbort )
      {
	 /* this is just a notification - remove qElem */
	 qElem			*x;

	 x = incomming;
	 incomming = incomming->next;
	 
	 if ( x->SR_APDU )
	    pe_free ( x->SR_APDU );
	 free ( x );
      }

      return opr;
   }

   /* wait for more data to arrive */
 again:;
   bcopy ( &allFileDescriptors, &ifds, sizeof ( fd_set ) );
   switch ( select ( 64/*FD_SETSIZE*/, &ifds, 0, 0, 0, timeout ) )
   {
    case -1:
      LOG ( facLow, llevExceptions, "select failed: %s", strerror() );
      return oprNone;

    case 0:
      return oprNone;		/* timeout */

    default:
      _TCPIP_HandleFileDescriptors ( &ifds, 0, 0 ); /* handle activity */

      /* check if sought operation now is ready */
      if ( _TCPIP_CheckIncommingQ ( ref, &qe ) )
      {	
	 SROperation		opr 		= qe->opr;
      
	 *ref = qe->ref;

	 if ( qe->opr == oprAbort )
	 {
	    /* this is just a notification - remove qElem */
	    qElem			*x;

	    x = incomming;
	    incomming = incomming->next;
	 
	    if ( x->SR_APDU )
	       pe_free ( x->SR_APDU );
	    free ( x );
	 }

	 return opr;
      }

      goto again;		/* should check if timeout has expired */
   }

   return oprNone;
}


/* 
 * Select on the masks passed - basically behaves as select(2)
 * Call this function only if ISODE is not used for selecting
*/

int _TCPIP_Select ( struct timeval *timeout,
		    int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds )
{
/*
 *  return number of filedescriptors with activity or 0 to indicate timeout 
 */
   Warn_UNIMPLEMENTED ( facLow, "_TCPIP_Select()" );
}


/*
 * Include generated macroes which contain all the SR_Send* and
 * SR_Read* functions.
 */

#include "sr_macroes_cpp.h"

Boolean _TCPIP_SendCloseRequest ( int ref, SRCloseRequest *crq )
{
   qElem				*e;
   qElem				*x, *p;

   close ( ref );		/* CLOSE */
   
   /* connection is now closed, fake close confirm indication to user */
   e = (qElem *) smalloc ( sizeof ( qElem ) );
   e->opr = oprCloseResponse;
   e->ref = ref;
   e->SR_APDU = NULLPE;
   e->oprPE = NULLPE;

   /* append the new APDU to the queue of incomming APDUs */
   for ( x = incomming, p = (qElem *) NULL; x; p = x, x = x->next );
   if ( !p )
      incomming = e;
   else
      p->next = e;
   e->next = (qElem *) NULL;

   return True;
}


Boolean _TCPIP_SendCloseResponse ( int ref, SRCloseResponse *crp )
{
   close ( ref );		/* Just in case... */
   
   FD_CLR ( ref, &allFileDescriptors );
   FD_CLR ( ref, &writeblockedFileDescriptors );
   tlv_close ( ref );

   return True;
}


SRCloseRequest *_TCPIP_ReadCloseRequest ( int ref )
{
   /* free _irs */
   incomming = incomming->next;

   return (SRCloseRequest *) smalloc ( sizeof ( struct SRCloseRequest ) );
}


SRCloseResponse *_TCPIP_ReadCloseResponse ( int ref )
{
   Iserver_sub_fd ( ref );

   /* free _irs */
   incomming = incomming->next;

   return (SRCloseResponse *) smalloc ( sizeof ( struct SRCloseResponse ) );
}

