/* UNIX Unified Stream Protocol 

   Copyright 1986,1996 by the Massachusetts Institute of Technology 
   See permission and disclaimer notice in file "notice.h" 
 */

/* First implementation, long ago in a galaxy far far away: Ted Kim
   Many bug fixes: Mark L. Lambert

   7/1/86 SRZ: added USP_make_connection and USP_associate calls.  
   USP_accept_connection now calls USP_associate.

   7/7/86 MLL: USP_accept_connection now takes fork flag and does not fork if
   flag value is FALSE.  This is for running USP applications under
   dbx.  
   Each interface operation now clears errno before beginning

   7/8/86 SRZ: Get_from_net bug fixed--a 0 return from recv used to cause an
   infinite loop if the other end disappeared suddenly.  Now
   errno is set to ECONNRESET and ERROR is returned...

   7/9/86 MLL: Cleaned up last of TK brain damage (I think) and fixed a bug
   in put_onto_net that caused it to return random garbage instead
   of SUCCESS/ERROR.  
   Punted USP_accept_connection
   Punted select/send/recv calls in favor of simple read and write
   calls
   Get_from_net does not return ERROR/ECONNRESET on 0 byte read
   return (turns out read call is allowed to return 0 bytes if no
   data happens to be available at the moment).  

   7/10/86 MLL: replaced single read/write socket in USPStream structure with
   a pair of FILE stream pointers, created via fdopen after a
   DUP call.  This allows buffered data sends with (presumably)
   fewer packet transmissions.  Replaced read/write with fread/
   fwrite
   Added USP_put/get_byte_block calls to get at raw USP block

   8/26/86 MLL: Implemented open-connection, connection-error, end, and
   end-reply blocks as per the USP spec.  Yuk.


   This library contains the following calls:

   Connection operations (main.c)

   (USPStream *) USP_associate((int))
   USP_close_connection(USPStream *))

   Block operations (block.c)

   USP_rcv_blk((USPStream *), (USPCardinal *))
   USP_begin_block((USPStream *), (USPCardinal))
   USP_end_block((USPStream *))
   USP_flush_block((USPStream *))

   Input operations (get.c)

   USP_get_boolean((USPStream *), (USPBoolean *))
   USP_get_integer((USPStream *), (USPInteger *))
   USP_get_cardinal((USPStream *), (USPCardinal *))
   USP_get_long_integer((USPStream *), (USPLong_integer *))
   USP_get_string((USPStream *), (USPString *))
   USP_get_byte_block((USPStream *), (Byte *), (unsigned), (unsigned *))

   Output operations (put.c)

   USP_put_boolean((USPStream *), (USPBoolean))
   USP_put_integer((USPStream *), (USPInteger))
   USP_put_cardinal((USPStream *), (USPCardinal))
   USP_put_long_integer((USPStream *), (USPLong_integer))
   USP_put_string((USPStream *), (USPString))
   USP_put_byte_block((USPStream *), (Byte *), (unsigned))

   Miscellaneous operations (block.c)

   (char *) usp_error((int))

 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "usp.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>

/* Generally Useful Things */

typedef unsigned short Boolean;
typedef char Byte;

extern int errno;

char *calloc ();

#define TRUE (Boolean) 1
#define FALSE (Boolean) 0
#define SUCCESS 1
#define ERROR   (-1)
#ifndef max
#define max(a, b) (a > b ? a : b)
#endif

/* internal routines */

static
get_from_net (USPStream * us, Byte * buf, unsigned req)
{
    int bytes_read;

    while (req) {
	if ((bytes_read = fread ((char *) buf, sizeof (Byte), (int) req,
				 us->us_read)) == 0) {
	    if (errno != 0)
		return ERROR;
	    else {
		errno = ECONNRESET; /* end of file -- conn reset */
		return ERROR;
	    }
	}
	req -= bytes_read;
	buf += bytes_read;
    }
    return SUCCESS;
}

/* put bytes onto network */

static
put_onto_net (USPStream * us, Byte * buf, unsigned len, Boolean send_p)
{
    int bytes_sent;

    bytes_sent = fwrite ((char *) buf, sizeof (Byte), (int) len,
			 us->us_write);
    if (bytes_sent != len)
	return ERROR;
    if (bytes_sent == 0 && errno != 0)
	return ERROR;
    if (send_p)
	fflush (us->us_write);
    return SUCCESS;
}

static
send_sub_block (USPStream * us, Boolean last_p)
{
    USPCardinal len = us->us_nsent_sub_block_bytes + sizeof (USPCardinal);

    /* is this the last sub-block? */
    if (last_p)
	/* then set last-sub-block flag */
	len |= EOB_FLAG;
    len = (USPCardinal) htons ((u_short) len);

    /* send length word */

    if (put_onto_net (us, (char *) &len, sizeof (USPCardinal), FALSE) == ERROR)
	return ERROR;

    /* and send outbuf */

    if (put_onto_net (us, (char *) us->us_outbuf, us->us_nsent_sub_block_bytes,
		      last_p) == ERROR)
	return ERROR;

    return SUCCESS;
}

static
get_sub_block_header (USPStream * us)
{
    USPCardinal sbl;		/* sub-block length */

    /* get sub-block length (12 bits) + last-sub-block flag (4 bits) */

    if (get_from_net (us, (char *) &sbl, sizeof (USPCardinal)) == ERROR)
	return (ERROR);
    sbl = (USPCardinal) ntohs ((u_short) sbl);
    us->us_last_sub_block_p = (sbl & EOB_FLAG) ? TRUE : FALSE;

    /* mask length to 12 bits less length of sub-block length field */

    us->us_nunread_sub_block_bytes = (sbl & 0x0FFF) - sizeof (USPCardinal);
    return (SUCCESS);
}

/* skip to end of sub-block */

static
skip_sub_block (us)
     USPStream *us;
{
    Byte buf[1024];

    if (us->us_nunread_sub_block_bytes == 0)
	return SUCCESS;
    while (us->us_nunread_sub_block_bytes > 1024) {
	if (get_from_net (us, buf, 1024) == ERROR)
	    return ERROR;
	us->us_nunread_sub_block_bytes -= 1024;
    }
    if (get_from_net (us, buf, us->us_nunread_sub_block_bytes) == ERROR)
	return ERROR;
    us->us_nunread_sub_block_bytes = 0;
    return SUCCESS;
}

/* get <req> bytes from the net, putting the bytes in <buf> and returning
   the number of bytes actually read in <bytes-actually-read>.  Assumes
   the current sub_block has had its header read via get_sub_block_header */

static
get_from_sub_block (us, buf, req, bytes_actually_read)
     USPStream *us;
     Byte *buf;
     unsigned req;
     unsigned *bytes_actually_read;
{
    /* read remainder of current sub-block plus whole sub-blocks until
       we have <req> bytes */

    *bytes_actually_read = 0;
    if (req == 0)
	return (SUCCESS);
    while (req > us->us_nunread_sub_block_bytes) {
	if (get_from_net (us, buf, us->us_nunread_sub_block_bytes) == ERROR)
	    return (ERROR);
	req -= us->us_nunread_sub_block_bytes;
	buf += us->us_nunread_sub_block_bytes;
	*bytes_actually_read += us->us_nunread_sub_block_bytes;
	if (us->us_last_sub_block_p) {
	    us->us_nunread_sub_block_bytes = 0;
	    return (SUCCESS);	/* block was shorter than we expected */
	}
	if (get_sub_block_header (us) == ERROR)
	    return (ERROR);
    }
    if (get_from_net (us, buf, req) == ERROR)
	return (ERROR);
    us->us_nunread_sub_block_bytes -= req;
    *bytes_actually_read += req;
    return (SUCCESS);
}

/* put the <len> bytes in <buf> into the current sub-block.  If the
   current sub-block fills up, send it and begin a new sub_block */

static
put_into_sub_block (us, buf, len)
     USPStream *us;
     Byte *buf;
     unsigned len;
{
    unsigned avail = MAX_SUB_BLOCK_LENGTH - us->us_nsent_sub_block_bytes;

    while (len > avail) {
	/* copy <avail> bytes of <buf> into <us_outbuf> and send <us_outbuf>
	   as a sub-block.  Thhen flush <us_outbuf> and put more bytes of
	   <buf> into it */

	memcpy (us->us_outbuf + us->us_nsent_sub_block_bytes, buf, avail);
	len -= avail;
	buf += avail;
	us->us_nsent_sub_block_bytes += avail;
	if (send_sub_block (us, FALSE) == ERROR)
	    return (ERROR);
	avail = MAX_SUB_BLOCK_LENGTH;
	us->us_nsent_sub_block_bytes = 0;
    }

    /* put rest of <buf> into <us->us_outbuf> */

    memcpy (us->us_outbuf + us->us_nsent_sub_block_bytes, buf, len);
    us->us_nsent_sub_block_bytes += len;
    return SUCCESS;
}

/* input operations */

USP_get_boolean (us, bo)
     USPStream *us;
     USPBoolean *bo;
{
    unsigned actual;

    errno = 0;
    if (!us->us_in_receiving_p) {
	errno = UENOTRCVING;
	return (ERROR);
    }
    if (get_from_sub_block (us, (char *) bo, sizeof (USPBoolean),
			    &actual) == ERROR)
	return (ERROR);
  if (actual != sizeof (USPBoolean)) {
      errno = UEPREMEOB;
      return (ERROR);
  }
  *bo = (USPBoolean) ntohs ((u_short) * bo);
  switch (*bo) {
  case TRUE:
  case FALSE:
      return (SUCCESS);
  default:
      errno = UEBADATA;
      return (ERROR);
  }
}

USP_get_integer (us, ui)
     USPStream *us;
     USPInteger *ui;
{
    unsigned actual;

    errno = 0;
    if (!us->us_in_receiving_p) {
	errno = UENOTRCVING;
	return (ERROR);
    }
    if (get_from_sub_block (us, (char *) ui, sizeof (USPInteger),
			    &actual) == ERROR)
	return (ERROR);
    if (actual != sizeof (USPInteger)) {
	errno = UEPREMEOB;
	return (ERROR);
    }
    *ui = (USPInteger) ntohs ((u_short) * ui);
    return (SUCCESS);
}

USP_get_cardinal (us, ca)
     USPStream *us;
     USPCardinal *ca;
{
    unsigned actual;

    errno = 0;
    if (!us->us_in_receiving_p) {
	errno = UENOTRCVING;
	return (ERROR);
    }
    if (get_from_sub_block (us, (char *) ca, sizeof (USPCardinal),
			    &actual) == ERROR)
	return (ERROR);
    if (actual != sizeof (USPCardinal)) {
	errno = UEPREMEOB;
	return (ERROR);
    }
    *ca = (USPCardinal) ntohs ((u_short) * ca);
    return (SUCCESS);
}

USP_get_long_integer (us, li)
     USPStream *us;
     USPLong_integer *li;
{
    unsigned actual;

    errno = 0;
    if (!us->us_in_receiving_p) {
	errno = UENOTRCVING;
	return (ERROR);
    }
    if (get_from_sub_block (us, (char *) li, sizeof (USPLong_integer),
			    &actual) == ERROR)
	return (ERROR);
    if (actual != sizeof (USPLong_integer)) {
	errno = UEPREMEOB;
	return (ERROR);
    }
    *li = (USPLong_integer) ntohl ((u_long) * li);
    return SUCCESS;
}

USP_get_string (us, str)
     USPStream *us;
     USPString *str;
{
    USPCardinal sl;		/* string length */
    register char *sptr, c;
    register char *dptr;
    Boolean oddp = FALSE;
    unsigned actual;

    errno = 0;
    if (!us->us_in_receiving_p) {
	errno = UENOTRCVING;
	return (ERROR);
    }
    if (get_from_sub_block (us, (char *) &sl, sizeof (USPCardinal),
			    &actual) == ERROR)
	return (ERROR);
    if (actual != sizeof (USPCardinal)) {
	errno = UEPREMEOB;
	return (ERROR);
    }
    sl = ntohs (sl);
    if (sl & 1)
	oddp = TRUE;
    if (!(*str = (USPString) malloc (sl + 1))) {
	errno = UENOMEM;
	return (ERROR);
    }
    dptr = sptr = *str;

    if (get_from_sub_block (us, sptr, sl + oddp, &actual) == ERROR) {
	free (*str);
	*str = NULL;
	return (ERROR);
    }
    if (actual != sl + oddp) {
	errno = UEPREMEOB;
	return ERROR;
    }
    /* de-netasciify */
    actual -= oddp;

    while (actual > 0) {
	--actual;
	c = *sptr++;
	if ((c == '\r') && (actual > 0)) {
	    --actual;
	    c = *sptr++;
	    if (c == '\012')
		*dptr++ = '\n';
	    else
		*dptr++ = '\r';
	}
	else
	    *dptr++ = c;
    }
    *dptr = '\0';

    /* if length is odd, discard the last byte */
    return SUCCESS;
}

/* this reads a raw block of bytes from a USP block.  For those of us who
   have no need of USP's data types... */

USP_get_byte_block (us, buf, len, actual)
     USPStream *us;
     Byte *buf;
     unsigned len;
     unsigned *actual;
{
    return get_from_sub_block (us, buf, len, actual);
}

/* output operations */

USP_put_boolean (us, bo)
     USPStream *us;
     USPBoolean bo;
{
    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }
    switch (bo) {
    case TRUE:
    case FALSE:
	break;
    default:
	errno = UEBADATA;
	return (ERROR);
    }
    bo = (USPBoolean) htons (bo);
    if (put_into_sub_block (us, (char *) &bo, sizeof (USPBoolean)) == ERROR)
	return (ERROR);
    return (SUCCESS);
}

USP_put_integer (us, ui)
     USPStream *us;
     USPInteger ui;
{
    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }
    ui = (USPInteger) htons ((u_short) ui);
    if (put_into_sub_block (us, (char *) &ui, sizeof (USPInteger)) == ERROR)
	return (ERROR);
    return (SUCCESS);
}

USP_put_cardinal (us, ca)
     USPStream *us;
     USPCardinal ca;
{
    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }
    ca = (USPCardinal) htons ((u_short) ca);
    if (put_into_sub_block (us, (char *) &ca, sizeof (USPCardinal)) == ERROR)
	return (ERROR);
    return (SUCCESS);
}

USP_put_long_integer (us, li)
     USPStream *us;
     USPLong_integer li;
{
    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }
    li = (USPLong_integer) htonl ((u_int) li);
    if (put_into_sub_block (us, (char *) &li, sizeof (USPLong_integer)) == ERROR)
	return (ERROR);
    return (SUCCESS);
}

USP_put_string (us, str)
     USPStream *us;
     USPString str;
{
    USPCardinal sl = 0, ssl;
    register char *sptr = str, *stptr;
    register int c;
    char zero = '\0';
    static const char crlf[] = "\r\n";
    static const char crnul[] = "\r\0";

    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }

    /* Process string one byte at a time in order to netasciify */

    /* first find netasciified length by adding 1 extra for every CR or NL */

    while (*sptr) {
	++sl;
	if (*sptr == '\r' || *sptr == '\n')
	    ++sl;
	++sptr;
    }
    ssl = (USPCardinal) htons ((u_short) sl);
    if (put_into_sub_block (us, (char *) &ssl, sizeof (USPCardinal)) == ERROR)
	return (ERROR);
    sptr = str;
    stptr = sptr;
    while ((c = *sptr) != '\0') {
	if (c == '\n') {
	    if (sptr > stptr
		&& put_into_sub_block (us, stptr, sptr - stptr) == ERROR)
		return ERROR;
	    if (put_into_sub_block (us, crlf, 2) == ERROR)
		return ERROR;
	    stptr = sptr + 1;
	} else if (c == '\r') {
	    if (sptr > stptr
		&& put_into_sub_block (us, stptr, sptr - stptr) == ERROR)
		return ERROR;
	    if (put_into_sub_block (us, crnul, 2) == ERROR)
		return ERROR;
	    stptr = sptr + 1;
	}
	++sptr;
    }
    if (stptr != sptr
	&& put_into_sub_block (us, stptr, sptr - stptr) == ERROR)
	return (ERROR);
    /* pad odd length strings */

    if ((sl & 1)
	&& put_into_sub_block (us, &zero, sizeof (char)) == ERROR)
	return (ERROR);
    return (SUCCESS);
}

/* this puts a raw block of bytes into a USP block.  For those of us who
   have no need of USP's data types... */

USP_put_byte_block (us, buf, len)
     USPStream *us;
     Byte *buf;
     unsigned len;
{
    return put_into_sub_block (us, buf, len);
}

/* USP block operations */

static char usp_internal_string[256];

static const char *const usp_setup_errlist[] =
{
  "foreign name unknown",
  "service identifier unknown",
  "host down",
  "service not supported on host",
  "service not currently available",
  "protocol conversion not available"};

static const char *const usp_during_errlist[] =
{
  "transport failure",
  "transport timeout",
  "foreign client failure",
  "path transformation impossible"};

USP_rcv_blk (us, bt)
     USPStream *us;
     USPCardinal *bt;
{
    errno = 0;

    /* get block type */

    if (get_from_net (us, (char *) bt, sizeof (USPCardinal)) == ERROR)
	return (ERROR);
    *bt = ntohs (*bt);
    us->us_in_receiving_p = TRUE;
    us->us_nunread_sub_block_bytes = 0;
    if (get_sub_block_header (us) == ERROR)
	return (ERROR);

    /* sneak a look at the block type.  If CONNECTION-ERROR,
       intercept it, parse the reason and return an error to
       the client */

    if (*bt == CONNECTION_ERROR) {
	USPCardinal what_error, who_reported;
	USPString info, module_name;
	char what_string[256], who_string[65];

	errno = UEINTERNAL;
	if (USP_get_cardinal (us, &what_error) == ERROR) {
	    USP_flush_block (us);
	    strcpy (usp_internal_string, "CONNECTION-ERROR block read error");
	    return (ERROR);
	}
	switch (what_error) {
	case CE_UNKNOWN:
	    strcpy (what_string, "unknown internal error");
	    break;
	case CE_NAME_UNKNOWN:
	case CE_SERVICE_UNKNOWN:
	case CE_HOST_DOWN:
	case CE_SERVICE_UNSUPPORTED:
	case CE_SERVICE_UNAVAILABLE:
	case CE_CONVERSION_UNAVAILABLE:
	    strcpy (what_string, usp_setup_errlist[what_error - CE_NAME_UNKNOWN]);
	    break;
	case CE_TRANSPORT_FAILURE:
	case CE_TRANSPORT_TIMEOUT:
	case CE_CLIENT_FAILURE:
	case CE_PATH_TRANSLATION:
	    strcpy (what_string, usp_during_errlist[what_error - CE_TRANSPORT_FAILURE]);
	    break;
	default:
	    sprintf (what_string, "(unknown internal error code %u)", what_error);
	    break;
	}
	if (USP_get_string (us, &info) == ERROR) {
	    USP_flush_block (us);
	    strcpy (usp_internal_string, "CONNECTION-ERROR block read error");
	    return (ERROR);
	}
	if (USP_get_cardinal (us, &who_reported) == ERROR) {
	    USP_flush_block (us);
	    strcpy (usp_internal_string, "CONNECTION-ERROR block read error");
	    return (ERROR);
	}
	switch (who_reported) {
	case FOREIGN_DETECT:
	    strcpy (who_string, "foreign client");
	    break;
	case TRANSPORT_DETECT:
	    strcpy (who_string, "transport layer");
	    break;
	case INTERMEDIATE_DETECT:
	    strcpy (who_string, "protocol converter");
	    break;
	default:
	    strcpy (who_string, "[unknown]");
	}
	if (USP_get_string (us, &module_name) == ERROR) {
	    USP_flush_block (us);
	    strcpy (usp_internal_string, "CONNECTION-ERROR block read error");
	    return (ERROR);
	}
	sprintf (usp_internal_string, "Internal error \"%s\" (%s), detected by %s \"%s\"",
		 what_string, info, who_string, module_name);
	free ((char *) info);
	free ((char *) module_name);
	return (ERROR);
    }

    /* if other end wishes to shut down, fine... */

    else if (*bt == CONNECTION_END) {
	USP_begin_block (us, CONNECTION_END_REPLY);
	USP_end_block (us);
	return CLOSED;
    }

    /* if other end is sending an OPEN block, ignore it and get the next
       block.  Note that we might recurse indefinitely if the other end
       never sends anything but CONNECTION-OPEN blocks... */

    else if (*bt == CONNECTION_OPEN) {
	USP_flush_block (us);
	return (USP_rcv_blk (us, bt));
    }
    return (SUCCESS);
}

USP_begin_block (USPStream * us, USPCardinal bt)
{
    errno = 0;
    if (us->us_out_sending_p) {	/* already sending a block! */
	errno = UECBNOTENDED;
	return (ERROR);
    }
    bt = htons (bt);
    if (put_onto_net (us, (char *) &bt, sizeof (USPCardinal), FALSE) == ERROR)
	return (ERROR);
    us->us_out_sending_p = TRUE;
    us->us_nsent_sub_block_bytes = 0;
    return (SUCCESS);
}

/* end block and flush buffer */

USP_end_block (USPStream * us)
{
    errno = 0;
    if (!us->us_out_sending_p) {
	errno = UENOTSENDING;
	return (ERROR);
    }
    if (send_sub_block (us, TRUE) == ERROR)
	return (ERROR);
    us->us_out_sending_p = FALSE;
    return (SUCCESS);
}

/* skip to end of block (does nothing if already there) */

void
USP_flush_block (USPStream * us)
{
    errno = 0;
    if (!us->us_in_receiving_p)
	return;
    while (!us->us_last_sub_block_p) {
	if (skip_sub_block (us) == ERROR)
	    return;
	if (get_sub_block_header (us) == ERROR)
	    return;
    }

    /* and skip over the last sub-block */

    if (skip_sub_block (us) == ERROR)
	return;
    us->us_in_receiving_p = FALSE;
}


/* connection operations */

/* associate a USP connection with an already open socket */

USPStream *
USP_associate (int s)
{
    USPStream *us;
    int write_desc;
    int on = 1;

    /* set up unified stream */

    if (!(us = (USPStream *) calloc (1, sizeof (USPStream))))
	return (NULL);
    /* (don't care much if this fails) */
    (void) setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on));
#if 0
    write_desc = dup (s);
#else
    write_desc = s;
#endif
    if (write_desc == ERROR) {
	free ((char *) us);
	return (NULL);
    }
    if (!(us->us_read = fdopen (s, "r"))) {
	free ((char *) us);
	return (NULL);
    }
    if (!(us->us_write = fdopen (write_desc, "w"))) {
	free ((char *) us);
	return (NULL);
    }
    us->us_in_receiving_p = FALSE;
    us->us_out_sending_p = FALSE;
    return us;
}

/* initiate the close of a unified stream connection */

USP_close_connection (USPStream * us)
{
    int status = SUCCESS;
    USPCardinal bt;

    errno = 0;

    /* start USP close sequence */


    if (USP_begin_block (us, CONNECTION_END) == ERROR)
	status = ERROR;
    if (USP_end_block (us) == ERROR)
	status = ERROR;

    /* wait for CONNECTION-END-REPLY block */

    if (USP_rcv_blk (us, &bt) == ERROR)
	status = ERROR;
    else if (bt != CONNECTION_END_REPLY)
	status = ERROR;
    else if (USP_begin_block (us, CONNECTION_END_REPLY) == ERROR)
	status = ERROR;
    else if (USP_end_block (us) == ERROR)
	status = ERROR;
    status = USP_shutdown (us);
    return (status);
}

USP_shutdown (us)
     USPStream *us;
{
    int status = SUCCESS;

    if (fclose (us->us_read) == EOF)
	status = ERROR;
    if (fclose (us->us_write) == EOF)
	status = ERROR;
    free ((char *) us);
    return (status);
}
