#ifndef lint
static char sccsid[] = "%W% (UKC) %G%";
#endif  lint
/*
 *	trans.c
 *	The primitives to send/receive bytes, chars, shorts, longs,
 *	strings and buffers of bytes.
 *	for virtual circuit transport service byte stream protocol (TSBSP)
 *
 *	Takes care of byte ordering and (eventually) ASCII-EBCDIC conversion.
 *	For the moment we stick to ASCII and LSB-first.
 *
 *	Data formats: typechecking byte followed by
 *	byte,char	[byte]
 *	short		[lsb][msb]
 *	long		[lsb][nxt][nxt][msb]
 *	buf		nbytes:long; nbytes*[byte]
 *	string:		strlen:long; strlen*[byte]
 *
 *	For the moment if you want to send unsigned, pretend that they're
 *	signed and hope that they use the same storage format.
 *
 *	There is copious use of abort()s in the case of internal errors
 *	so that we get a corefile whence we can find the culprit.
 *
 *	Functions herein which call other send/recv functions can decrement
 *	and increment checking so that one typechecking token is sent per
 *	data object (or none or many).
 *	It does not matter which funtions do this reducing the typeckecking by
 *	onr level as long as pairs of send/recv functions do the same thing.
 */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/tsbsp.h>
#include <sys/tserrno.h>
#include "trans.h"
#include "libtsb.h"

char *malloc();

#define BMASK 0377	/* the bottom 8 bits */

/* Types */
#define MINTYPE	1
#define BYTE	1
#define	CHAR	2
#define SHORT	3
#define LONG	4
#define BUF	5
#define STRING	6
#define STAT	7
#define VSTRUCT 8
#define MAXTYPE	8

/* Macros for fast min/max from /usr/include/sys/param.h */
#ifndef MIN
# define	MIN(a,b)	(((a)<(b))?(a):(b))
# define	MAX(a,b)	(((a)>(b))?(a):(b))
#endif

#ifdef DEBUG
static char *typename[MAXTYPE-MINTYPE+1] = {
	"byte", "char", "short", "long", "buf", "string", "stat", "vstruct"
};
#endif

static char rpcbget();
static int rpcread();

static char obuf[BUFSIZ];	/* use sizeof(obuf) henceforth, not BUFSIZ */
static char *obufptr = obuf;	/* where to put the next byte into obuf */
static int obufcnt = sizeof(obuf); /* no of unfilled bytes left in obuf */

static char ibuf[BUFSIZ];
static char *ibufptr;	/* points to next byte to be returned */
static int ibufcnt = 0;	/* No of valid bytes left in ibuf before we must read again */
			/* "= 0" causes a fill of buffer to be first action. This sets ibufptr. */

#ifdef TYPECHECKING
/*
 *	Because high level primitives call low level primitives, and both are
 *	available to the calling program, typechecking of data bytes is more
 *	sophisticated than on/off. With it normally on, it is initialised to 1
 *	then each higher level routine decrements it after doing typechecking,
 *	which prevents the routines *it* calls from doing so, then increments
 *	it afterwards.
 *
 *	downcheck reduces the level of typechecking
 *	upcheck inceases it again. They should occur in matched pairs.
 */
	static int checking = 1;	/* typechecking ON, one level deep */

#	define sendcheck(type)	if (checking > 0) rpcbput(tsfd, (type))
#	define recvcheck(type)	if (checking > 0) typecheck(tsfd, (type))
#	define downcheck()	checking--;
#	define upcheck()	checking++;
#else
#	define sendcheck(type)	;
#	define recvcheck(type)	;
#	define downcheck()	;
#	define upcheck()	;
#endif

/*
 *	{send,recv}<type> routines are the top level interface to this module.
 *	{send,recv}byte deal with raw 8-bit patterns
 *	{send,recv}char may undergo character set mapping
 *	Similar conditions apply to {send,recv}buf and string.
 */

sendbyte(tsfd,ch)
register char ch;
{
	sendcheck(BYTE);
	rpcbput(tsfd,ch);
}

char
recvbyte(tsfd)
{
	recvcheck(BYTE)
	return(rpcbget(tsfd));
}

sendchar(tsfd,ch)
register char ch;
{
	sendcheck(CHAR);
	rpcbput(tsfd,ch);
}

char
recvchar(tsfd)
{
	recvcheck(CHAR);
	return(rpcbget(tsfd));
}

sendshort(tsfd,s)
register short s;
{
	sendcheck(SHORT);
	rpcbput(tsfd, (char) (s & BMASK) );
	rpcbput(tsfd, (char) ( (s>>8) & BMASK ) );
}

short
recvshort(tsfd)
{
	register short s;

	recvcheck(SHORT);
	s = (short) rpcbget(tsfd) & BMASK;
	s = s | ( ( (short) rpcbget(tsfd) & BMASK) << 8);
	return(s);
}

sendlong(tsfd,l)
register long l;
{
	sendcheck(LONG);
	rpcbput(tsfd, (char) (l & BMASK) );
	rpcbput(tsfd, (char) ((l >>= 8) & BMASK) );
	rpcbput(tsfd, (char) ((l >>= 8) & BMASK) );
	rpcbput(tsfd, (char) ((l >>= 8) & BMASK) );
}

long
recvlong(tsfd)
{
	register long l;

	recvcheck(LONG);
	/* Coercion happens before logical and */
	l = (long) rpcbget(tsfd) & BMASK;
	l = l | ( ( (long) rpcbget(tsfd) & BMASK) << 8);
	l = l | ( ( (long) rpcbget(tsfd) & BMASK) << 16);
	l = l | ( ( (long) rpcbget(tsfd) & BMASK) << 24);
	return(l);
}

sendstring(tsfd,buf,len)
char *buf;
{
	/* len should include the null if it is a normal string */

	register long l;	/* loop counter */

	sendcheck(STRING);
	downcheck();

	sendlong(tsfd,(long) len);
	for (l=0; l<len; l++)
		sendchar(tsfd,buf[l]);
	upcheck();
}

recvstring(tsfd,bufp,len)
char **bufp;
{
	register long l;	/* loop counter */
	register long nbytes;	/* length of received string */

	recvcheck(STRING);
	downcheck();
	nbytes = recvlong(tsfd);
	if (len < 0) {
		*bufp = malloc ((unsigned) nbytes);
	} else if (nbytes > len)
		fatal(tsfd,TE_FAIL, "recvstring too long\n");
	for (l=0; l<nbytes; l++)
		(*bufp)[l] = recvchar(tsfd);
	upcheck();
	return(nbytes);
}

/* naughty sendbuf pokes the data directly into the buffer if it can */

sendbuf(tsfd,buf, nbytes)
char *buf;
{
	register long n;

	sendcheck(BUF);
	downcheck();
	sendlong(tsfd,(long) nbytes);

/* Cannot do clever stuff easily if we need to insert TC bytes */
#ifdef TYPECHECK
	if (checking <= 0) {
#endif
		while (nbytes > 0) {
			if (obufcnt == 0) {
				tsdata(tsfd, obuf, sizeof(obuf));
				obufptr = obuf;
				obufcnt = sizeof(obuf);
			}
			n = MIN(obufcnt, nbytes);
			bcopy(buf, obufptr, n);
			obufptr += n;	obufcnt -=n;
			buf += n;	nbytes -= n;
		}
#ifdef TYPECHECK
	} else {	/* boo - boring! */
		for (n=0; n<nbytes; n++)
			sendbyte(tsfd,buf[n]);
	}
#endif
	upcheck();
}

/*
 * Receive buffer of bytes; Note extra layer of indirection of buffer pointer.
 * If (len < 0) we are to malloc the appropriate amount of space for the buffer
 * received and set what buf points to to the address of this buffer
 *
 * Returns the actual number of bytes inserted into the buffer.
 */

 recvbuf(tsfd,bufp, len)
 char **bufp;
 {
	register long nbytes;	/* Size of buffer actually received */

	recvcheck(BUF);
	downcheck();

	nbytes = recvlong(tsfd);
	if (len < 0) {
		*bufp = malloc ((unsigned) nbytes);
		if (*bufp == NULL)
			fatal(tsfd,TE_FAIL, "Out of memory\n");
	} else if (nbytes > len)
		fatal (tsfd, TE_FAIL, "recvbuf too long\n");

#ifdef TYPECHECK
	if (checking <= 0)
#endif
	{
		/* Fast version yomps blocks straight from the input buffer */
		register char *bp = *bufp;	/* where to put the bytes */
		register long n;		/* how many to chomp */

		/* while we still want some... */
		while (nbytes > 0) {
			/* fill input buffer if it is empty, */
			if (ibufcnt == 0) {
				ibufcnt = rpcread(tsfd, ibuf, sizeof(ibuf));
				ibufptr = ibuf;
			}
			/* and copy across as much as we can. */
			n = MIN(ibufcnt, nbytes);
			bcopy(ibufptr, bp, n);
			ibufptr += n;	ibufcnt -= n;
			bp += n;	nbytes -= n;
		}
	}
#ifdef TYPECHECK
	else {	
		/* slow and steady does it */
		register long l;	/* loop counter */

		for (l=0; l<nbytes; l++)
			(*bufp)[l] = recvbyte(tsfd);
	}
#endif
	upcheck();
	return(nbytes);
}

#ifdef TYPECHECK
static
typecheck(tsfd,shouldbe)
char shouldbe;
{
	register char is;

	if (shouldbe == (is = rpcbget(tsfd))) return;
	else {
		char mesg[64];
		fatal(tsfd,TE_FAIL, sprintf(mesg,
#ifdef DEBUG
			"Typeclash; expected %s, got %d.\n",
			typename[shouldbe-MINTYPE], is)
#else
			"Typeclash %d not %d.\n",
			shouldbe, is)
#endif
		);
	}
}
#endif

/*
 *	rpcbput, rpcbget do write-behind, read-ahead and buffering a la stdio
 */

static
rpcbput(tsfd,ch)
char ch;
{
	if (obufcnt == 0) {
		if (tsdata(tsfd, obuf, sizeof(obuf)) != 0)
			rpcfail(tsfd, "rpcbput: write error");
		obufcnt = sizeof(obuf);
		obufptr = obuf;
	}
	*obufptr++ = ch;
	obufcnt--;
}

/*
 *	Flush pending output with a PUSH (eg before a read)
 */

rpcflush(tsfd)
{
	if (tspush(tsfd, obuf, obufptr-obuf) != 0)
		rpcfail(tsfd, "tspush write error");
	obufptr = obuf;
	obufcnt = sizeof(obuf);
}

/*
 *	rpgbget: return data bytes as though from a bytestream
 */

static char
rpcbget(tsfd)
{
	while (ibufcnt == 0) {
		ibufcnt = rpcread(tsfd,ibuf, sizeof(ibuf));
		/*
		 * rpcread either returns a good value of goes scrrrgnnnxx
		 * so no checking of return value is necessary
		 */
		ibufptr = ibuf;
	}
	ibufcnt--;
	return(*ibufptr++);
}

/*
 *	rpcread: read a data buffer from transport service.
 *	DISCONNECTS and other spuriae are reacted to and dealt with here.
 */

static int
rpcread(tsfd,buf, nbytes)
char *buf;
{
	tscmd_t tin;
	char explan[256];

	/* flush output buffer so that remote server will respond */
	if (obufptr > obuf) rpcflush(tsfd);

	tin.ts_par[0].ts_data = buf;
	tin.ts_par[0].ts_nbytes = nbytes;
	tin.ts_par[1].ts_data = (char *) 0;
	tin.ts_par[1].ts_nbytes = 0;
	tin.ts_par[2].ts_data = explan;
	tin.ts_par[2].ts_nbytes = sizeof(explan);
	tin.ts_par[3].ts_data = (char *) 0;
	tin.ts_par[3].ts_nbytes = 0;

	if (read(tsfd, (char *) &tin, sizeof(tin)) != sizeof(tin))
		fatal(tsfd,TE_FAIL, "rpcread error\n");
	
	switch(tin.ts_command) {
	case TS_DATA:
	case TS_PUSH:
		return(tin.ts_par[0].ts_nbytes);

	case TS_DISCONNECT:
		tsresponse(tsfd, TS_DISCONNECT);
		switch (tin.ts_par[0].ts_data[0]) {
		case TE_OK:
			exit(0);
		
		case TE_SORRY:
			/* Protocol error - from source, also known as
			 * "User has died".  This most often happens at UKC
			 * with the troff server when the remote process
			 * hits its CPU time limit.
			 *
			 * Hack: do the right thing.
			 */
			kill(getpid(), SIGXCPU);
			/* If *that* doesn't work... */
			fputs("CPU time limit exceeded.\n", stderr);
			exit(1);

		default: 
			fprintf(stderr, "Lost contact with troff server: %s - %s.\n", tserror((int) tin.ts_par[0].ts_data[0]), explan);
		}
		exit(1);

	case TS_RESET:
		/*
		 *	If we get a reset we may have lost a byte of data
		 *	so there's not much we can do to recover
		 */
		fatal(tsfd,TE_RESET, "Net RESET\n");

	default:
		{
			char mesg[64];
			fatal(tsfd,TE_YOURBAD, sprintf(mesg, "ts_command %d?\n", tin.ts_command));
		}
	}
#ifdef lint
	return(-1);
#endif
}

/*
 *	Close down the transport service (EXPORTED)
 */
rpcexit(tsfd)
{
	tsdisconnect(tsfd, TE_OK, (char *) 0, (char *) 0);
}

/*
 *	exit because of software failure or runtime fault (EXPORTED)
 */
rpcfail(tsfd,explan)
char *explan;
{
	fatal(tsfd,TE_FAIL, explan);
}

/*
 *	Tell anyone who'll listen what went wrong and die
 */
static
fatal(tsfd,reason, explan)
char *explan;
{
	(void) fputs(explan,stderr);
	tsdisconnect(tsfd, reason, (char *) 0, explan);
	abort();
}

/*
 *	some library functions to send large objects
 */

/*
 *	send and recieve struct stat
 */

#include <sys/stat.h>

sendstat(tsfd,stbuf)
struct stat *stbuf;
{
	sendcheck(STAT);
	downcheck();
	sendshort(tsfd,(short) stbuf->st_dev);	/* dev_t */
	sendlong (tsfd,(long) stbuf->st_ino);	/* ino_t */
	sendshort(tsfd,(short) stbuf->st_mode);	/* u_short */
	sendshort(tsfd,stbuf->st_nlink);
	sendshort(tsfd,stbuf->st_uid);
	sendshort(tsfd,stbuf->st_gid);
	sendshort(tsfd,(short) stbuf->st_rdev);	/* dev_t */
	sendlong (tsfd,(long) stbuf->st_size);	/* off_t */
	sendlong (tsfd,(long) stbuf->st_atime);	/* time_t */
	sendlong (tsfd,(long) stbuf->st_mtime);	/* time_t */
	sendlong (tsfd,(long) stbuf->st_ctime);	/* time_t */
	sendlong (tsfd,stbuf->st_blksize);
	sendlong (tsfd,stbuf->st_blocks);
	upcheck();
}

recvstat(tsfd,stbuf)
struct stat *stbuf;
{
	recvcheck(STAT);
	downcheck();
	stbuf->st_dev	  = recvshort(tsfd);	/* dev_t */
	stbuf->st_ino	  = recvlong (tsfd);	/* ino_t */
	stbuf->st_mode	  = recvshort(tsfd);	/* u_short */
	stbuf->st_nlink	  = recvshort(tsfd);
	stbuf->st_uid	  = recvshort(tsfd);
	stbuf->st_gid	  = recvshort(tsfd);
	stbuf->st_rdev	  = recvshort(tsfd);	/* dev_t */
	stbuf->st_size	  = recvlong (tsfd);	/* off_t */
	stbuf->st_atime	  = recvlong (tsfd);	/* time_t */
	stbuf->st_mtime	  = recvlong (tsfd);	/* time_t */
	stbuf->st_ctime	  = recvlong (tsfd);	/* time_t */
	stbuf->st_blksize = recvlong (tsfd);
	stbuf->st_blocks  = recvlong (tsfd);
	upcheck();
}

/*
 *	Send and receive an argv-type structure.
 *	Used to get argv and environment structures.
 *	Pass arguments to process on the other side
 *	as one data buffer: argc:short followed by the concatenated
 *	string arguments, delimited by null bytes.
 */

sendvstruct(tsfd,argc, argv)
short argc;
char **argv;
{
	register short i;

	sendcheck(VSTRUCT);
	downcheck();
	sendshort(tsfd,argc);
	for (i=0; i<argc; i++) {
		sendstring(tsfd,argv[i],strlen(argv[i])+1); /* one for the null */
	}
	upcheck();
}

char **
recvvstruct(tsfd)
{
	register short argc;
	register short i;
	char **argv;

	recvcheck(VSTRUCT);
	downcheck();
	argc = recvshort(tsfd);

	/* Plus one for the null pointer which terminates the list */
	argv = (char **) malloc ((unsigned) (argc+1) * sizeof(char *));
	if (argv == NULL) {
		fputs("Out of memory!\n", stderr);
		exit(1);
	}
	for (i=0; i<argc; i++) {
		(void) recvstring(tsfd,&(argv[i]),-1);
	}
	argv[argc] = (char *) 0;
	upcheck();
	return(argv);
}
