/* Higher level user subroutines built on top of the socket primitives
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by PA0GRI
 */
#include "global.h"
#include <stdarg.h>
#include "mbuf.h"
#include "proc.h"
#include "socket.h"
#ifdef	LZW
#include "lzw.h"
#else
#include "usock.h"
#endif
#include "session.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: sockuser.c,v 1.34 2000/05/09 14:18:55 brian Exp $";
#endif


extern void backEmUp (int len);
int sockblock (int s, int value);
extern const char hitEnter[];

#ifdef FIFOSERVER
extern int FIFOout;
#endif


/* Higher-level receive routine, intended for connection-oriented sockets.
 * Can be used with datagram sockets, although the sender id is lost.
 */
int
recv (s, buf, len, flags)
int s;				/* Socket index */
char *buf;			/* User buffer */
int len;			/* Max length to receive */
int flags;			/* Unused; will eventually select oob data, etc */
{
struct mbuf *bp;
int cnt;

	if (len == 0)
		return 0;	/* Otherwise would be interp as "all" */

	cnt = recv_mbuf (s, &bp, flags, NULLCHAR, (int *) NULL);
	if (cnt > 0) {
		cnt = min (cnt, len);
		(void) pullup (&bp, (unsigned char *) buf, (int16) cnt);
		free_p (bp);
	}
	return cnt;
}



/* Higher level receive routine, intended for datagram sockets. Can also
 * be used for connection-oriented sockets, although from and fromlen are
 * ignored.
 */
int
recvfrom (s, buf, len, flags, from, fromlen)
int s;				/* Socket index */
char *buf;			/* User buffer */
int len;			/* Maximum length */
int flags;			/* Unused; will eventually select oob data, etc */
char *from;			/* Source address, only for datagrams */
int *fromlen;			/* Length of source address */
{
struct mbuf *bp;
register int cnt;

	cnt = recv_mbuf (s, &bp, flags, from, fromlen);
	if (cnt > 0) {
		cnt = min (cnt, len);
		(void) pullup (&bp, (unsigned char *) buf, (int16) cnt);
		free_p (bp);
	}
	return cnt;
}



/* High level send routine */
int
send (s, buf, len, flags)
int s;				/* Socket index */
char *buf;			/* User buffer */
int len;			/* Length of buffer */
int flags;			/* Unused; will eventually select oob data, etc */
{
register struct mbuf *bp;
char sock[MAXSOCKSIZE];
int i = MAXSOCKSIZE;

	if (getpeername (s, sock, &i) == -1)
		return -1;
	bp = qdata ((unsigned char *) buf, (int16) len);
	return send_mbuf (s, bp, flags, sock, i);
}



/* High level send routine, intended for datagram sockets. Can be used on
 * connection-oriented sockets, but "to" and "tolen" are ignored.
 */
int
sendto (s, buf, len, flags, to, tolen)
int s;				/* Socket index */
char *buf;			/* User buffer */
int len;			/* Length of buffer */
int flags;			/* Unused; will eventually select oob data, etc */
char *to;			/* Destination, only for datagrams */
int tolen;			/* Length of destination */
{
register struct mbuf *bp;

	bp = qdata ((unsigned char *) buf, (int16) len);
	return send_mbuf (s, bp, flags, to, tolen);
}



/* Receive a newline-terminated line from a non-blocking input socket,
 * returning # chars read. The end-of-line sequence is recognized and
 * translated into a single '\n'.
 */
int
nb_recvline (s, buf, len)
int s;				/* Socket index */
unsigned char *buf;		/* User buffer */
unsigned len;			/* Length of buffer */
{
int c;
int cnt = 0;

	while (len-- > 1) {
		while ((c = recvchar (s)) == EOF) {
			if (errno != EWOULDBLOCK)
				return -1;
			
			if (kpause (500) == -1)
				return -1;
		}
		if (buf != (unsigned char *) 0)
			*buf++ = uchar(c);
		cnt++;
		if (uchar (c) == '\n')
			break;
	}
	if (buf != (unsigned char *) 0)
		*buf = '\0';
	return cnt;
}



/* Receive a newline-terminated line from a socket, returning # chars read.
 * The end-of-line sequence is recognized and translated into a single '\n'.
 */
int
recvline (s, buf, len)
int s;				/* Socket index */
unsigned char *buf;		/* User buffer */
unsigned len;			/* Length of buffer */
{
int c;
int cnt = 0;

	while (len-- > 1) {
		if ((c = recvchar (s)) == EOF) {
			cnt = -1;
			break;
		}
		if (buf != (unsigned char *) 0)
			*buf++ = uchar(c);
		cnt++;
		if (uchar (c) == '\n')
			break;
	}
	if (buf != (unsigned char *) 0)
		*buf = '\0';
	return cnt;
}



/* Do printf on a user socket */
int
usprintf (int s, const char *fmt,...)
{
va_list args;
int len;

	va_start (args, fmt);		/*lint !e718 !e746 */
	len = usvprintf (s, fmt, args);
	va_end (args);
	return len;
}


/* Printf on standard output socket */
int
tprintf (const char *fmt,...)
{
va_list args;
int len;

	va_start (args, fmt);
	len = usvprintf (Curproc->output, fmt, args);
	va_end (args);
	return len;
}


#if defined(SCREENSAVER) && defined(UNIX)
int16 intrace = 0;
#endif

#ifdef TRACE
extern int tracesock;


/* Print on Tracesession stdout socket */
void
traceprintf (FILE * fp, const char *fmt,...)
{
va_list args;

#if defined(SCREENSAVER) && defined(UNIX)
	intrace = 1;
#endif
	va_start (args, fmt);
	if (fp == stdout)	{
		(void) usvprintf (tracesock, fmt, args);
		(void) usflush (tracesock);
	} else
		(void) vfprintf (fp, fmt, args);
	va_end (args);
#if defined(SCREENSAVER) && defined(UNIX)
	intrace = 0;
#endif
}

#endif


#ifdef UNIX
extern void sm_status (int pos, char *str);
#endif


/* New style - use session manager switch's status entry point */
int
tcmdprintf (const char *fmt,...)
{
va_list args;
char *buf;
int len;

	buf = mallocw (SOBUF);
	va_start (args, fmt);
	if ((len = VSPRINTF ((buf, fmt, args))) >= SOBUF) {
		log (-1, "tcmdprintf() buffer overflow");
		where_outta_here (2, "tcmdprintf");
	}
	va_end (args);
#ifdef UNIX
	sm_status (0, buf);
#else
	usputs (Command->proc->output, buf);
#endif
	free (buf);
	return len;
}



#ifdef UNIX
/* Print on Command stdout socket or a file */
int
tfprintf (FILE * fp, const char *fmt,...)
{
va_list args;
int len;

	va_start (args, fmt);
	if (fp == stdout)
		len = usvprintf (Command->output, fmt, args);
	else
		len = vfprintf (fp, fmt, args);
	va_end (args);
	return len;
}

#endif




/* The guts of printf, uses variable arg version of sprintf */
int
usvprintf (int s, const char *fmt, va_list args)
{
int len;
char *buf;

	if (strchr (fmt, '%') == NULLCHAR) {
		/* Common case optimization: no args, so we don't
		 * need vsprintf()
		 */
		buf = strdup (fmt);
		len = (int) strlen (fmt);
	} else {
		/* Use a default value that is hopefully longer than the
		 * biggest output string we'll ever print (!)
		 */
		buf = mallocw (SOBUF);
		/* Start of mod by N4YYH */
		if ((len = VSPRINTF ((buf, fmt, args))) >= SOBUF) {
			/* It's too late to be sorry. He's dead, Jim. */
			log (s, "usvprintf() has exceeded the size of it's buffer. Restarting NOS.");
			where_outta_here (2, "usvprintf");
		}
		/* End of mod by N4YYH */
	}
	if (usputs (s, buf) == EOF)
		len = -1;
	free (buf);
	return len;
}


/* Buffered putchar to a socket */
int
usputc (int s, const unsigned char c)
{
struct usock *up;
register struct mbuf *bp;
unsigned char *cp;
/*lint -esym(550,len) */
int newline, len;

	if (nullsocket (s)) {
		errno = EBADF;
		return -1;
	}
	up = itop (s);
	if (c == '\n' && (up->flag & SOCK_ASCII)) {
		newline = 1;
		len = (int) strlen (up->eol);
	} else {
		newline = 0;
		len = 1;
	}
#ifndef LZW
	/* Make sure there's room in the current buffer, if any */
	if ((bp = up->obuf) != NULLBUF) {
		if ((bp->cnt >= bp->size - len) && usflush (s) == -1)
			return EOF;
	}
#endif
	if (up->obuf == NULLBUF) 	/* Allocate a buffer of appropriate size */
		up->obuf = ambufw (BUFSIZ);

	/* Note: the buffer must be larger than the end-of-line sequence! */
	bp = up->obuf;
	cp = &bp->data[bp->cnt];
	if (newline) {
		/* Translate into appropriate end-of-line sequence */
#ifdef	LZW
		for (cp = (unsigned char *) up->eol; *cp != '\0'; cp++)
			if (up->zout == NULLLZW)
				bp->data[bp->cnt++] = *cp;
			else
				lzwencode (s, (char) *cp);
#else
		strncpy (cp, up->eol, len);
#endif
	} else {
#ifdef	LZW
		if (up->zout == NULLLZW)
			bp->data[bp->cnt++] = c;
		else
			lzwencode (s, (char) c);
	}
#else
		*cp = c;
	}
	bp->cnt += len;
#endif
	/* Flush if necessary and leave enough room for a coming eol */
	if ((c == up->flush && up->flush != -1) || bp->cnt >= bp->size - 9)
		if (usflush (s) == -1)
			return -1;

#ifdef LOOKSESSION
	if (up->look)		/* Some one is looking at us ! */
		usputc (up->look->output, c);
#endif
	return (int) uchar (c);
}
/*lint +esym(550,len) */



/* Put a character to standard output socket */
int
tputc (unsigned char c)
{
	return usputc (Curproc->output, c);
}



#ifndef	oldusputs
/* Buffered puts to a socket */
int
usputs (int s, const char *buf)
{
register struct usock *up;
register struct mbuf *bp;
unsigned char *cp, *wp;
int16 len, clen;
int doflush;
int eol_len = 0;
int16 flushpt;
int ascii;
#ifdef LOOKSESSION
const char *oldbuf;
#endif

#ifdef FIFOSERVER
	if (s < SOCKBASE && s == FIFOout)	{
		write (s, buf, strlen (buf));
		return 0;
	}
#endif
	if (nullsocket (s)) {
		errno = EBADF;
		return EOF;
	}
	up = itop (s);
#ifdef LOOKSESSION
	oldbuf = buf;
#endif
#ifdef	LZW
	if (up->zout != NULLLZW) {
		while (*buf != '\0')
			if (usputc (s, uchar(*buf++)) == EOF)
				return EOF;
		return 0;
	}
#endif
	ascii = up->flag & SOCK_ASCII;
	if (ascii)
		eol_len = (int) strlen (up->eol);
	doflush = (up->flush != -1) && (strchr (buf, up->flush) != NULLCHAR);
	len = (int16) strlen (buf);

	while (len != 0) {
		if (up->obuf == NULLBUF) {
			/* Allocate a buffer of appropriate size */
			clen = BUFSIZ;
			up->obuf = ambufw (clen);
		}
		/* Note: the buffer must be larger than the end-of-line sequence! */
		bp = up->obuf;
		wp = &bp->data[bp->cnt];
#ifdef TNOS_68K
		if (ascii && (cp = strchr (buf, '\l')) != (unsigned char *) 0) {
#else
		if (ascii && (cp = (unsigned char *) strpbrk (buf, "\n")) != (unsigned char *) 0) {
#endif
			/* Copy up to, but not including, newline */
			clen = (int16) ((const char *) cp - buf);
		} else 	/* Copy whole thing */
			clen = len;

		/* ...but no more than the room available */
		clen = min (clen, bp->size - bp->cnt);
		if (clen != 0) {
			memcpy (wp, buf, (size_t) clen);
			wp += clen;
			bp->cnt += clen;
			buf += clen;
			len -= clen;
		}
		/* Set flush threshold to allow for eol, if enabled */
		if (ascii)
			flushpt = (int16) (bp->size - eol_len);
		else
			flushpt = bp->size;

		if (ascii && (*buf == '\n') && bp->cnt < flushpt) {
			/* Add appropriate end-of-line sequence */
			strncpy ((char *) wp, up->eol, (size_t) eol_len);
			wp += eol_len;
			bp->cnt += (int16) eol_len;
			buf++;	/* Skip newline in buffer */
			len--;
		}
		if (bp->cnt >= flushpt) {
			/* Buffer full, flush and get another */
			if (usflush (s) == -1)
				return EOF;
		}
	}
	if (doflush && usflush (s) == -1)
		return EOF;

#ifdef LOOKSESSION
	if (up->look)		/* someone is looking at us ! */
		usputs (up->look->output, oldbuf);
#endif
	return 0;
}

#else


int
usputs (int s, register const char *x)
{
	while (*x != '\0')
		if (usputc (s, *x++) == EOF)
			return EOF;
	return 0;
}

#endif




#ifdef FBBFWD
int 
usputbuf (int s, register const unsigned char *x, int len)
{
	while (--len >= 0)
		if (usputc (s, *x++) == EOF)
			return EOF;
	return 0;
}

#endif



/* Put a string to standard output socket */
int
tputs (const char *s)
{
	return usputs (Curproc->output, s);
}



/* Read a raw character from a socket with stream buffering. */
int
rrecvchar (s)
int s;				/* Socket index */
{
register struct usock *up;
register int c;

	if (nullsocket (s))
		return EOF;
	up = itop (s);
#ifdef	LZW
	if (up->zin != NULLLZW)
		if ((c = lzwdecode (up)) != -1)
			return c;
#endif
#if 1
	if (up->insertbuf) {
		c = *up->insertptr++;
		if (!*up->insertptr) {
			free (up->insertbuf);
			up->insertbuf = NULLCHAR;
		}
		return c;
	}
#endif
	/* Replenish if necessary */
	if (up->ibuf == NULLBUF && recv_mbuf (s, &up->ibuf, 0, NULLCHAR, 0) <= 0)
		return EOF;

#ifdef	LZW
	if (up->zin != NULLLZW)	{
		if ((c = lzwdecode (up)) != -1)
			return c;
		else
			return rrecvchar (s);	/* needs replenish */
	}
#endif
	c = PULLCHAR (&up->ibuf);	/* returns -1 if eof */

	return (c == -1 ? EOF : c);
}



/* This function recognizes the end-of-line sequence for the stream
 * and translates it into a single '\n'.
 */
int
recvchar (s)
int s;				/* Socket index */
{
int c;
register struct usock *up;
int inserted;

	if (nullsocket (s))
		return EOF;

	up = itop (s);
	inserted = (up->insertbuf) ? 1 : 0;
	c = rrecvchar (s);

	if (c != up->eol[0] || !(up->flag & SOCK_ASCII)) {
#ifdef LOOKSESSION
		if (Current != Command && up->look && !inserted && c != EOF)
			usputc (up->look->output, uchar(c));
#endif
		return c;
	}
	/* This is the first char of a eol sequence. If the eol sequence is
	 * more than one char long, eat the next character in the input stream.
	 */
	if (up->eol[1] != '\0')
		(void) rrecvchar (s);

#ifdef LOOKSESSION
	if (up->look && !inserted) {
		usputc (up->look->output, '\n');
		/* In the case 'looking' is done from bbs connection,
		 * flush it here... */
		usflush (up->look->output);
	}
#endif
	return '\n';
}



/* Flush output on a socket stream */
int
usflush (int s)
{
register struct usock *up;
struct mbuf *bp;

	if (nullsocket (s))
		return -1;

	up = itop (s);
	if (up->obuf != NULLBUF) {
#ifdef	LZW
		if (up->zout != NULLLZW)
			lzwflush (up);
#endif
		bp = up->obuf;
		up->obuf = NULLBUF;
		return send_mbuf (s, bp, 0, NULLCHAR, 0);
	}
#ifdef LOOKSESSION
	if (up->look)		/* Some one is looking at us, flush them as well */
		usflush (up->look->output);
#endif
	return 0;
}



/* Flush output socket */
void
tflush ()
{
	usflush (Current->output);
}



/* Print prompt and read one character */
int
keywait (prompt, flush)
const char *prompt;		/* Optional prompt */
int flush;			/* Flush queued input? */
{
int c;
const char *pm;
int block = SOCK_BLOCK;

	if (prompt == NULLCHAR)
		block = sockblock (Curproc->input, SOCK_BLOCK);
	if (flush && socklen (Curproc->input, 1) != 0)
		(void) recv_mbuf (Curproc->input, NULLBUFP, 0, NULLCHAR, 0);	/* flush */
	pm = (prompt == NULLCHAR) ? hitEnter : prompt;
	tputs (pm);
	tflush ();
	c = recvchar (Curproc->input);
	backEmUp ((int) strlen (pm));
	if (prompt == NULLCHAR)
		(void) sockblock (Curproc->input, block);
	return (int) c;
}



/* Set the end-of-line sequence on a socket */
int
seteol (int s, const char *seq)
{
register struct usock *up;

	/* Do *NOT* use nullsocket() here, since this MAY
	   be being called while the socket is being
	   constructed, and will BE null at the time. */
	if ((up = itop (s)) == NULLUSOCK)
		return -1;

	if (seq != NULLCHAR)
		strncpy (up->eol, seq, sizeof (up->eol));
	else
		*up->eol = '\0';
	return 0;
}



/* Enable/disable eol translation, return previous state */
int
sockmode (int s, int mode)
{
struct usock *up;
int prev;

	/* Do *NOT* use nullsocket() here, since this is
	   sometimes called while the socket is being
	   constructed, and may BE null at the time. */
	if ((up = itop (s)) == NULLUSOCK)
		return -1;
	usflush (s);
	prev = up->flag;
	switch (mode) {
		case SOCK_BINARY:
		case SOCK_ASCII:
			up->flag = mode;
			break;
		default:
			break;
	}
	return prev;
}



/* Specify the character to trigger automatic output buffer
 * flushing, or -1 to disable it. Return the previous setting.
 */
int
setflush (int s, int c)
{
register struct usock *up;
int old;

	if (nullsocket (s))
		return -1;

	up = itop (s);
	old = up->flush;
	up->flush = c;
	return old;
}



/* Set the block mode on a socket - WG7J.
 * Primarily used on convers.c to prevent backlog of data and
 * usprintf() calls blocking because of it...
 * returns previous mode
 */
int 
sockblock (int s, int value)
{
register struct usock *up;
int oldval;

	if (nullsocket (s)) {
		errno = EBADF;
		return -1;
	}
	up = itop (s);
	oldval = up->noblock;
	up->noblock = (char) value;
	return oldval;
}



int 
getblock (int s)
{
register struct usock *up;

	if (nullsocket (s)) {
		errno = EBADF;
		return -1;
	}
	up = itop (s);
	return up->noblock;
}



int
nullusock (struct usock *up)
{
	if (up == NULLUSOCK || !up->cb.p) {
		errno = ENOTCONN;
		return 1;
	}
	return 0;
}


int
nullsocket (int s)
{
	return nullusock (itop (s));
}
