/* Higher level user subroutines built on top of the socket primitives
 * Copyright 1991 Phil Karn, KA9Q
 */
#include "global.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);
	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(s,c)
int s;
char c;
{
	struct usock *up;
	register struct mbuf *bp;
	char *cp;

	if((up = itop(s)) == NULLUSOCK){
		errno = EBADF;
		return -1;
	}
	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;
		}
	}
	bp = up->obuf;
	if(c == '\n' && (up->flag & SOCK_ASCII)){
		/* Translate into appropriate end-of-line sequence */
		for(cp = up->eol;*cp != '\0';cp++)
			bp->data[bp->cnt++] = *cp;
	} else {
		bp->data[bp->cnt++] = c;
	}
	/* Always leave enough room for an eol sequence in the next call */
	if((c == up->flush && up->flush != -1) || bp->cnt >= bp->size-2)
		if(usflush(s) == -1)
			return -1;

	return (int)uchar(c);
}
/* Put a character to standard output socket */
int
tputc(c)
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;
	int newline;

	if((up = itop(s)) == NULLUSOCK){
		errno = EBADF;
		return EOF;
	}
	if(up->flag & SOCK_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);
		}
		bp = up->obuf;
		wp = &bp->data[bp->cnt];
		if((up->flag && SOCK_ASCII)
		 && (cp = strchr(buf,'\n')) != NULLCHAR){
			newline = 1;

			/* Ensure space for the eol sequence */
			if(bp->cnt + eol_len >= bp->size){
				/* Not enough room; flush and
				 * go back for another buffer
				 */
				if(usflush(s) == -1)
					return EOF;
				continue;
			}
			/* Copy only up to newline, allowing space for eol */
			clen = cp - buf;
			clen  = min(clen,bp->size - bp->cnt - eol_len);
		} else {
			newline = 0;
			clen = min(len,bp->size - bp->cnt);
		}
		/* Copy as much data as possible to mbuf */
		if(clen != 0){
			memcpy(wp,buf,clen);
			wp += clen;
			bp->cnt += clen;
			buf += clen;
			len -= clen;
		}
		if(newline){
			/* Translate into 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 >= bp->size) && 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);

	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;
}
