/****************************************************************************
*	sockuser.c
*	$Id: sockuser.c 1.3 94/01/04 14:10:37 ROOT_DOS Exp $
*																	
*	01 Apr 93	1.2		GT	recvchar () - skip ^Z in ASCII mode.
*	07 Nov 93	1.3		GT	Redisplay "net>" prompt in command session.
*
*  ATARI Version by David Nash - dnash@chaos.demon.co.uk
*
****************************************************************************/

/* Higher level user subroutines built on top of the socket primitives
 * Copyright 1991 Phil Karn, KA9Q
 */
#include "global.h"
#include "main.h"
#ifdef	ANSIPROTO
#include <stdarg.h>
#endif
#include "mbuf.h"
#include "proc.h"
#include "socket.h"
#include "usock.h"
#include "session.h"
#include "nr4.h"


/* 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);
		pullup(&bp,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);
		pullup(&bp,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(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(buf,(int16)len);
	return send_mbuf(s,bp,flags,to,tolen);
}
/* 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 */
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 != NULLCHAR)
			*buf++ = c;
		cnt++;
		if(uchar(c) == '\n')
			break;
	}
	if(buf != NULLCHAR)
		*buf = '\0';
	return cnt;
}
#if	defined(ANSIPROTO)
/* Do printf on a user socket */
int
usprintf(int s,char *fmt,...)
{
	va_list args;
	int len;

	va_start(args,fmt);
	len = usvprintf(s,fmt,args);
	va_end(args);
	return len;
}
/* Printf on standard output socket */
int
tprintf(char *fmt,...)
{
	va_list args;
	int len;

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

#if 0
	/* Refresh "net> " prompt if necessary. */

	if (Curproc->output == Command->output)
		{
		(void) usprintf (Curproc->output, Prompt);
		usflush (Curproc->output);
		}
#endif

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

	if(strchr(fmt,'%') == NULLCHAR){
		/* Common case optimization: no args, so we don't
		 * need vsprintf()
		 */
		withargs = 0;
		buf = fmt;
		len = strlen(fmt);
	} else {
		/* Use a default value that is hopefully longer than the
		 * biggest output string we'll ever print (!)
		 */
		withargs = 1;
		buf = mallocw(SOBUF);
		vsprintf(buf,fmt,args);
		len = strlen(buf);
	}
	if(usputs(s,buf) == EOF)
		len = -1;
	if(withargs)
		free(buf);
	return len;
}
#else
/*VARARGS*/
/* Printf to standard output socket */
int
tprintf(fmt,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12)
char *fmt;		/* Message format */
int arg1,arg2,arg3;	/* Arguments */
int arg4,arg5,arg6;
int arg7,arg8,arg9;
int arg10,arg11,arg12;
{
	return usprintf(Curproc->output,fmt,arg1,arg2,arg3,arg4,arg5,arg6,
		arg7,arg8,arg9,arg10,arg11,arg12);
}
/* Printf to socket. Doesn't use ANSI vsprintf */
int
usprintf(s,fmt,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12)
int s;			/* Socket index */
char *fmt;		/* Message format */
int arg1,arg2,arg3;	/* Arguments */
int arg4,arg5,arg6;
int arg7,arg8,arg9;
int arg10,arg11,arg12;
{
	int len,withargs;
	char *buf;

	if(strchr(fmt,'%') == NULLCHAR){
		/* No args, so we don't need vsprintf() */
		withargs = 0;
		buf = fmt;
		len = strlen(fmt);
	} else {
		/* Use a default value that is hopefully longer than the
		 * biggest output string we'll ever print (!)
		 */
		withargs = 1;
		buf = mallocw(SOBUF);
		sprintf(buf,fmt,arg1,arg2,arg3,arg4,arg5,arg6,arg7,
		 arg8,arg9,arg10,arg11,arg12);
		len = strlen(buf);
	}
	if(usputs(s,buf) == EOF)
		len = -1;

	if(withargs)
		free(buf);
	return len;
}
#endif
/* Buffered putchar to a socket */

int usputc(
	int s,
	char c )
{
	struct usock *up;
	register struct mbuf *bp;
	char *cp;
	int newline,len;

	if((up = itop(s)) == NULLUSOCK){
		errno = EBADF;
		return -1;
	}
	if(c == '\n' && (up->flag & SOCK_ASCII)){
		newline = 1;
		len = strlen(up->eol);
	} else {
		newline = 0;
		len = 1;
	}
	/* 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;
	}
	if(up->obuf == NULLBUF){
		/* Allocate a buffer of appropriate size */
		switch(up->type){
		case TYPE_NETROML4:
			up->obuf = ambufw(NR4MAXINFO);
			break;
		default:
			up->obuf = ambufw(BUFSIZ);
			break;
		}
	}
	/* 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 */
		strncpy(cp,up->eol,len);
	} else {
		*cp = c;
	}
	bp->cnt += len;
	/* Flush if necessary */
	if(c == up->flush && up->flush != -1 && usflush(s) == -1)
			return -1;

	return (int)uchar(c);
}
/* Put a character to standard output socket */

int tputc(char c)
{
	return usputc(Curproc->output,c);
}
#ifndef	oldusputs
/* Buffered puts to a socket */
int
usputs(s,buf)
int s;
char *buf;
{
	register struct usock *up;
	register struct mbuf *bp;
	char *cp,*wp;
	int16 len,clen;
	int doflush;
	int eol_len;
	int16 flushpt;
	int ascii;

	if((up = itop(s)) == NULLUSOCK){
		errno = EBADF;
		return EOF;
	}
	ascii = up->flag & SOCK_ASCII;
	if(ascii)
		eol_len = strlen(up->eol);
	doflush = (up->flush != -1) && (strchr(buf,up->flush) != NULLCHAR);
	len = strlen(buf);

	while(len != 0){
		if(up->obuf == NULLBUF){
			/* Allocate a buffer of appropriate size */
			switch(up->type){
			case TYPE_NETROML4:
				clen = NR4MAXINFO;
				break;
			default:
				clen = BUFSIZ;
				break;
			}
			up->obuf = ambufw(clen);
		}
		/* Note: the buffer must be larger than the end-of-line sequence! */
		bp = up->obuf;
		wp = &bp->data[bp->cnt];
		if(ascii && (cp = strchr(buf,'\n')) != NULLCHAR){
			/* Copy up to, but not including, newline */
			clen = 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,clen);
			wp += clen;
			bp->cnt += clen;
			buf += clen;
			len -= clen;
		}
		/* Set flush threshold to allow for eol, if enabled */
		if(ascii)
			flushpt = bp->size - eol_len;
		else
			flushpt = bp->size;

		if(ascii && *buf == '\n' && bp->cnt < flushpt){
			/* Add appropriate end-of-line sequence */
			strncpy(wp,up->eol,eol_len);
			wp += eol_len;
			bp->cnt += 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;

	return 0;
}

#else

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

/* Put a string to standard output socket */
int
tputs(s)
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;

	if((up = itop(s)) == NULLUSOCK){
		return EOF;
	}
	/* Replenish if necessary */
	if(up->ibuf == NULLBUF && recv_mbuf(s,&up->ibuf,0,NULLCHAR,0) <= 0)
		return EOF;

	return PULLCHAR(&up->ibuf);	/* Returns -1 if eof */
}
/* 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;

	if((up = itop(s)) == NULLUSOCK)
		return EOF;

	c = rrecvchar(s);

	while (c == '\032' && (up->flag & SOCK_ASCII) != 0)
		c = rrecvchar (s);				/* skip embedded ^Z					*/

	if(c != up->eol[0] || !(up->flag & SOCK_ASCII))
		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);
	}
	return '\n';
}
/* Flush output on a socket stream */
int
usflush(s)
int s;
{
	register struct usock *up;
	struct mbuf *bp;

	if((up = itop(s)) == NULLUSOCK)
		return -1;

	if(up->obuf != NULLBUF){
		bp = up->obuf;
		up->obuf = NULLBUF;
		return send_mbuf(s,bp,0,NULLCHAR,0);
	}
	return 0;
}
/* Flush output socket */
void
tflush()
{
	usflush(Current->output);
}

/* Print prompt and read one character */
int
keywait(prompt,flush)
char *prompt;	/* Optional prompt */
int flush;	/* Flush queued input? */
{
	int c;
	int i;

	if(flush && socklen(Curproc->input,1) != 0)
		recv_mbuf(Curproc->input,NULLBUFP,0,NULLCHAR,0); /* flush */
	if(prompt == NULLCHAR)
		prompt = "Hit enter to continue"; 
	tprintf(prompt);
	tflush();
	c = recvchar(Curproc->input);
	/* Get rid of the prompt */
	for(i=strlen(prompt);i != 0;i--)
		tputc('\b');
	for(i=strlen(prompt);i != 0;i--)
		tputc(' ');
	for(i=strlen(prompt);i != 0;i--)
		tputc('\b');
	tflush();
	return (int)c;
}

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

	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(s,mode)
int s,mode;
{
	struct usock *up;
	int prev;

	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(s,c)
int s;
int c;
{
	register struct usock *up;
	int old;

	if((up = itop(s)) == NULLUSOCK)
		return -1;

	old = up->flush;
	up->flush = c;
	return old;
}
