/***************************************
  $Revision: 1.2 $

  Socket module - cd_socket.c - basic read/write socket routines defined
                                in terms of connection data structures
				with timeouts and storing information about
				broken connections.

  Status: NOT REVUED, TESTED

  Design and implementation by Marek Bukowy.

  ******************/ /******************
  Copyright (c) 1999, 2000                           RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/
#include "sk.h"
#include "stubs.h"
#include "memwrap.h"
/*+
 * -------------------------------------------------------------------
 * CD (connection data structure) varieties of the functions: 
 *   broken connections get registered in the connection structure
 *   as side effects.
 * by marek
 * -----------------------------------------------------------------
+*/

/* SK_cd_make */
/*++++++++++++++++++++++++++++++++++++++
  
  Construct a connection data given the socket or file descriptor.
  Also performs the getpeername check and stores the IP in an allocated
  string.

  sk_conn_st *condat    pointer to where the data is to be stored.

  int  sock             The socket or file descriptor.

  unsigned timeout      Read timeout (used in SK_cd_gets) in seconds.
                        Value of 0 disables the timeout.
  ++++++++++++++++++++++++++++++++++++++*/
void SK_cd_make(sk_conn_st *condat, int  sock, unsigned timeout)
{
  memset(condat, 0, sizeof(sk_conn_st));

  condat->sock = sock;

  condat->ip = SK_getpeername(sock); 
  dieif(condat->ip == NULL);

  SK_getpeerip(sock, &(condat->rIP));
  condat->eIP =  condat->rIP;

  condat->rd_timeout.tv_sec = timeout;
}


/*++++++++++++++++++++++++++++++++++++++
  Destroys the data allocated and anchored by the connection data structure.

  sk_conn_st *condat   Pointer to the connection data structure.

  ++++++++++++++++++++++++++++++++++++++*/
void SK_cd_free(sk_conn_st *condat)
{
  wr_free(condat->ip);
}

/* SK_cd_puts() */
/*++++++++++++++++++++++++++++++++++++++

   This function writes a character string out to a socket, unless 
   the connection is broken. 

   int SK_cd_puts         Returns the total_count of bytes written, 
                          or inverted error codes (negative numbers):
			  (- SK_DISCONNECT) on broken connection,
			  (- SK_INTERRUPT)  on control-c received,
			  (- SK_TIMEOUT)    on timeout.

   sk_conn_st *condat     Pointer to the connection data structure.

   char   *str            The buffer to be written to the socket.

  More:
       if the connection structure has bad status for this connection
       from previous calls, no write will be attempted.

  +html+ <PRE>
  Author:
        marek

  Side Effects:
       broken connections get registered in the connection structure 
	
  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_puts(sk_conn_st *condat, const char *str) 
{
  int res;
  
  if( condat->rtc != 0 ) {
    return (-condat->rtc);
  }

  res = SK_puts(condat->sock, str);

  if( res < 0 ){
    /* set the corresponding rtc flag */
    condat->rtc |= (-res);

    switch( - res ) {
      /* dont know what to do and how to log */
    case SK_DISCONNECT:
    case SK_INTERRUPT:
      /*("Thread received a control-c\n");*/
    case SK_TIMEOUT:
      /*("Reading timed out\n");*/
      break;
    default:
      /* unexpected error code. bail out */
      die;
    }
  }
  return res;
} /* SK_cd_puts() */

/* SK_cd_gets() */
/*++++++++++++++++++++++++++++++++++++++

  Read from a socket, until a linefeed character is received or the buffer
  fills up to the maximum size "count". If the connection data has non-zero
  timeout value for reading, it is used here between calls to read 
  the next 1 character.

   int SK_cd_gets      Returns the total_count of bytes read, 
                       or inverted error codes (negative numbers):
		       (- SK_DISCONNECT) on broken connection,
		       (- SK_TIMEOUT)    on timeout.

   sk_conn_st *condat  connection data

   char   *str         The buffer to store the data received from
                       the socket.

   size_t count        size of the buffer.

  More:
       if the connection structure has bad status for this connection
       from previous calls, no read will be attempted.

  +html+ <PRE>
  Author:
        marek
	
  Side Effects:
       broken connections get registered in the connection structure.
       
  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_gets(sk_conn_st *condat, char *str, size_t count) 
{
  fd_set rset;
  struct timeval *ptm = & condat->rd_timeout;
  int readcount = 0;
    
  memset( str, 0, count);
  /* leave space for \0 */
  count--;

  FD_ZERO( &rset );
  FD_SET( condat->sock, &rset );

  if( ptm->tv_sec == 0 && ptm->tv_usec == 0) { /* if timeout undefined, 
						  do blocking I/O */
    ptm = NULL;
  }

  do {
    char buf[2];
    int sel = select( (condat->sock)+1, &rset, NULL, NULL, ptm);
    int ern = errno;

    dieif(sel < 0); /* we don't expect problems */
      
    if( sel == 0 ) {      
      condat->rtc |= SK_TIMEOUT;
      break;
    }

    else { 
      if(read( condat->sock, buf, 1) == 0 ) {
	condat->rtc |= SK_DISCONNECT;
	break;
      }
      str[readcount] = buf[0];
      readcount++;
      if( buf[0] == '\n' ) {
	break;
      }
    } 
  } while( readcount < count );
	 
  return readcount;

} /* SK_cd_gets() */


/*++++++++++++++++++++++++++++++++++++++
  Wrapper around the close(2) system call, 

  int SK_cd_close         returns the error codes of close(2).

  sk_conn_st *condat      Pointer to the connection data structure.

    +html+ <PRE>
  Author:
        marek
    +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_close(sk_conn_st *condat) {
  return SK_close(condat->sock);
} /* SK_cd_close() */


/* SK_cd_printf() */
/*++++++++++++++++++++++++++++++++++++++

  Printf-like function to print to socket/file specified by connection
  data structure. First writes the text to a temporary buffer, then
  uses SK_cd_puts to print it. Maintains a 2K static buffer, and allocates
  more memory if this is not enough.

  int SK_cd_printf       Returns the SK_cd_puts error code/return value.

  sk_conn_st *condat     Pointer to the connection data structure.

  char *txt              Format text to be written

  ...                    more arguments (like printf)

  
    +html+ <PRE>
  Author:
        marek
    +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_printf(sk_conn_st *condat, char *txt, ...)
{
#define SKBUFLEN 2047
  va_list   ap;
  char      buffer[SKBUFLEN+1];
  unsigned  len;
  char      *newbuf = NULL;
  char      *finalbuf = buffer; /* points to where the text REALLY is */
 
  /* vsnprintf returns the number of character it WOULD write if it could.
     So we assume the buffer to be of adequate size for most cases,
     and if it isn't, then we allocate to newbuf and call v*printf again 
  */
  va_start(ap, txt);
  len = vsnprintf(buffer, SKBUFLEN, txt, ap);
  va_end(ap);
  
  if( len > SKBUFLEN ) {
    dieif(!NOERR(wr_malloc( (void **)& newbuf, len+1)));
    
    va_start(ap, txt);
    vsnprintf(newbuf, len, txt, ap);
    va_end(ap);   
    
    finalbuf = newbuf;
  }  
  /* terminate */
  finalbuf[len] = 0;

  /* reuse len */
  len = SK_cd_puts(condat, finalbuf);

  if(newbuf != NULL) {
    wr_free(newbuf);
  }

  return len;
} /* SK_cd_printf() */
