/* net/rom user command processing
 * Copyright 1989 by Daniel M. Frank, W9NK.  Permission granted for
 * non-commercial distribution only.
 */
/* Mods by G1EMM and PA0GRI */

#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include "config.h"
#include "global.h"
#include "mbuf.h"
#include "ax25.h"
#include "mailbox.h"
#include "netrom.h"
#include "nr4.h"
#include "timer.h"
#include "iface.h"
#include "pktdrvr.h"
#include "lapb.h"
#include "cmdparse.h"
#include "session.h"
#include "socket.h"
#include "commands.h"
#include "files.h"

char Nr4user[AXALEN];

char *Nr4states[] = {
	"Disconnected",
	"Conn Pending",
	"Connected",
	"Disc Pending",
	"Listening"
} ;

char *Nr4reasons[] = {
	"Normal",
	"By Peer",
	"Timeout",
	"Reset",
	"Refused"
} ;
static int dobcnodes __ARGS((int argc,char *argv[],void *p));
static int dointerface __ARGS((int argc,char *argv[],void *p));
static int donfadd __ARGS((int argc,char *argv[],void *p));
static int donfdrop __ARGS((int argc,char *argv[],void *p));
static int donfdump __ARGS((void));
static int donfmode __ARGS((int argc,char *argv[],void *p));
static int donodefilter __ARGS((int argc,char *argv[],void *p));
static int donodetimer __ARGS((int argc,char *argv[],void *p));
static int donracktime __ARGS((int argc,char *argv[],void *p));
static int donrchoketime __ARGS((int argc,char *argv[],void *p));
static int donrconnect __ARGS((int argc,char *argv[],void *p));
static int donrirtt __ARGS((int argc,char *argv[],void *p));
static int donrkick __ARGS((int argc,char *argv[],void *p));
static int dorouteadd __ARGS((int argc,char *argv[],void *p));
static int doroutedrop __ARGS((int argc,char *argv[],void *p));
static int donrqlimit __ARGS((int argc,char *argv[],void *p));
static int donrreset __ARGS((int argc,char *argv[],void *p));
static int donrretries __ARGS((int argc,char *argv[],void *p));
static int donrroute __ARGS((int argc,char *argv[],void *p));
static int donrstatus __ARGS((int argc,char *argv[],void *p));
static int donrsave __ARGS((int argc,char *argv[],void *p));
static int donrload __ARGS((int argc,char *argv[],void *p));
static int donrttl __ARGS((int argc,char *argv[],void *p));
static int donruser __ARGS((int argc,char *argv[],void *p));
static int donrverbose __ARGS((int argc,char *argv[],void *p));
static int donrwindow __ARGS((int argc,char *argv[],void *p));
static void doobsotick __ARGS((void));
static int doobsotimer __ARGS((int argc,char *argv[],void *p));
static int dominquality __ARGS((int argc,char *argv[],void *p));
static int donrtype __ARGS((int argc,char *argv[],void *p));
static int donrpromisc __ARGS((int argc,char *argv[],void *p));
static int donrderate __ARGS((int argc,char *argv[],void *p));

static struct cmds Nrcmds[] = {
	"acktime",	donracktime,	0, 0,	NULLCHAR,
	"bcnodes",	dobcnodes,	0, 2,	"netrom bcnodes <interface>",
	"connect",	donrconnect, 1024, 2,	"netrom connect <node>",
	"choketime",	donrchoketime,	0, 0,	NULLCHAR,
	"derate",	donrderate,	0, 0,	NULLCHAR,
	"interface",	dointerface,	0, 4,
		"netrom interface <interface> <alias> <quality>",
	"irtt",		donrirtt,	0, 0,	NULLCHAR,
	"kick",		donrkick,	0, 2,	"netrom kick <&nrcb>",
	"load",		donrload,	0, 0,	NULLCHAR,
	"minquality",	dominquality,	0, 0,	NULLCHAR,
	"nodefilter",	donodefilter,	0, 0,	NULLCHAR,
	"nodetimer",	donodetimer,	0, 0,	NULLCHAR,
	"obsotimer",	doobsotimer,	0, 0,	NULLCHAR,
	"promiscuous",	donrpromisc,	0, 0,	NULLCHAR,
	"qlimit",	donrqlimit,	0, 0,	NULLCHAR,
	"reset",	donrreset,	0, 2,	"netrom reset <&nrcb>",
	"retries",	donrretries,	0, 0,	NULLCHAR,
	"route",	donrroute,	0, 0,	NULLCHAR,
	"status",	donrstatus,	0, 0,	NULLCHAR,
	"save",		donrsave,	0, 0,	NULLCHAR,
	"timertype",	donrtype,	0, 0,	NULLCHAR,
	"ttl",		donrttl,	0, 0,	NULLCHAR,
	"user",		donruser,	0, 0,	NULLCHAR,
	"verbose",	donrverbose,	0, 0,	NULLCHAR,
	"window",	donrwindow,	0, 0,	NULLCHAR,
	NULLCHAR,
} ;

static struct timer Nodetimer ;	/* timer for nodes broadcasts */
static struct timer Obsotimer ;	/* timer for aging routes */

/* Command multiplexer */
int
donetrom(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return subcmd(Nrcmds,argc,argv,p) ;
}

static struct cmds Routecmds[] = {
	"add",	dorouteadd,	0, 6,
		"netrom route add <alias> <destination> <interface> <quality> <neighbor>",
	"drop",	doroutedrop, 0, 4,
		"netrom route drop <destination> <neighbor> <interface>",
	"info", dorouteinfo, 0, 2,
		"netrom route info <destination>",
	NULLCHAR,
} ;

/* Route command multiplexer */
static int
donrroute(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	if(argc < 2) {
		doroutedump() ;
		return 0 ;
	}
	return subcmd(Routecmds,argc,argv,p) ;
}

/* Dump a list of known routes */
int
doroutedump()
{
	register struct nrroute_tab *rp ;
	register int i, column ;
	char buf[16] ;
	char *cp ;
	
	column = 1 ;
	
	for(i = 0 ; i < NRNUMCHAINS ; i++)
		for(rp = Nrroute_tab[i] ; rp != NULLNRRTAB ; rp = rp->next) {
			strcpy(buf,rp->alias) ;
			/* remove trailing spaces */
			if((cp = strchr(buf,' ')) == NULLCHAR)
				cp = &buf[strlen(buf)] ;
			if(cp != buf)		/* don't include colon for null alias */
				*cp++ = ':' ;
			pax25(cp,rp->call) ;
			tprintf("%-16s  ",buf) ;
			if(column++ == 4) {
				if(tprintf("\n") == EOF)
					return 0;
				column = 1 ;
			}
		}

	if(column != 1)
		tprintf("\n") ;
		
	return 0 ;
}

/* print detailed information on an individual route */
int
dorouteinfo(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	register struct nrroute_tab *rp ;
	register struct nr_bind *bp ;
	register struct nrnbr_tab *np ;
	char dest[AXALEN] ;
	char neighbor[AXBUF] ;

	if(setcall(dest,argv[1]) == -1) {
		tprintf("bad destination name\n") ;
		return -1 ;
	}
		
	if((rp = find_nrroute(dest)) == NULLNRRTAB) {
		tprintf("no such route\n") ;
		return -1 ;
	}

	for(bp = rp->routes ; bp != NULLNRBIND ; bp = bp->next) {
		np = bp->via ;
		if(tprintf("%1s %3d  %3d  %-8s  %s\n",
		 (bp->flags & NRB_PERMANENT ? "P" :
		 bp->flags & NRB_RECORDED ? "R" : " "),
		 bp->quality,bp->obsocnt,
		 Nrifaces[np->iface].iface->name,
		 pax25(neighbor,np->call)) == EOF)
			break;
	}
	return 0 ;
}
		
/* convert a null-terminated alias name to a blank-filled, upcased */
/* version.  Return -1 on failure. */
int
putalias(to,from,complain)
register char *to, *from ;
int complain ;    
{
	int len, i ;
	
	if((len = strlen(from)) > ALEN) {
		if(complain)
			tprintf("alias too long - six characters max\n") ;
		return -1 ;
	}
	
	for(i = 0 ; i < ALEN ; i++) {
		if(i < len) {
			if(islower(*from))
				*to++ = toupper(*from++) ;
			else
				*to++ = *from++ ;
		}
		else
			*to++ = ' ' ;
	}
			
	*to = '\0' ;
	return 0 ;
}

/* Add a route */
static int
dorouteadd(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	char alias[AXALEN] ;
	char dest[AXALEN] ;
	unsigned quality ;
	char neighbor[AXALEN] ;
	register int i ;
	int naddr ;

	/* format alias (putalias prints error message if necessary) */
	if(putalias(alias,argv[1],1) == -1)
		return -1 ;

	/* format destination callsign */
	if(setcall(dest,argv[2]) == -1) {
		tprintf("bad destination callsign\n") ;
		return -1 ;
	}

	/* find interface */
	for(i = 0 ; i < Nr_numiface ; i++)
		if(!strcmp(Nrifaces[i].iface->name,argv[3]))
			break ;
	if(i == Nr_numiface) {
		tprintf("Interface \"%s\" not found\n",argv[3]) ;
		return -1 ;
	}
	
	/* get and check quality value */
	if((quality = atoi(argv[4])) > 255) {
		tprintf("maximum route quality is 255\n") ;
		return -1 ;
	}

	/* Change from 871225 -- no digis in net/rom table */
	naddr = argc - 5 ;
	if(naddr > 1) {
		tprintf("Use the ax25 route command to specify digipeaters\n") ;
		return -1 ;
	}
	
	/* format neighbor address string */
	setcall(neighbor,argv[5]) ;

	return nr_routeadd(alias,dest,i,quality,neighbor,1,0) ;
}


/* drop a route */
static int
doroutedrop(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	char dest[AXALEN], neighbor[AXALEN] ;
	register int i ;

	/* format destination and neighbor callsigns */
	if(setcall(dest,argv[1]) == -1) {
		tprintf("bad destination callsign\n") ;
		return -1 ;
	}
	if(setcall(neighbor,argv[2]) == -1) {
		tprintf("bad neighbor callsign\n") ;
		return -1 ;
	}

	/* find interface */
	for(i = 0 ; i < Nr_numiface ; i++)
		if(!strcmp(Nrifaces[i].iface->name,argv[3]))
			break ;
	if(i == Nr_numiface) {
		tprintf("Interface \"%s\" not found\n",argv[3]) ;
		return -1 ;
	}

	return nr_routedrop(dest,neighbor,i) ;
}
	
	
/* make an interface available to net/rom */
static int
dointerface(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	int i ;
	register struct iface *ifp ;

	if(Nr_iface == NULLIF) {
		tprintf("Attach netrom interface first\n") ;
		return 1 ;
	}
	
	if(Nr_numiface >= NRNUMIFACE) {
		tprintf("Only %d net/rom interfaces available\n",NRNUMIFACE) ;
		return 1 ;
	}
	
	if((ifp = if_lookup(argv[1])) == NULLIF){
		tprintf("Interface \"%s\" unknown\n",argv[1]);
		return 1;
	}
	if( ifp->type != CL_AX25){
		tprintf("Interface %s is not NETROM compatible\n",argv[1]);
		return 1;
	}
	for(i = 0 ; i < Nr_numiface ; i++)
		if(Nrifaces[i].iface == ifp) {
			tprintf("Interface \"%s\" is already registered\n",argv[1]) ;
			return 1 ;
		}
		
	Nrifaces[Nr_numiface].iface = ifp ;

	if(putalias(Nrifaces[Nr_numiface].alias,argv[2],1) == -1)
		return 1 ;
		
	if((Nrifaces[Nr_numiface].quality = atoi(argv[3])) > 255) {
		tprintf("Quality cannot be greater than 255\n") ;
		return 1 ;
	}
		
	Nr_numiface++ ;			/* accept this interface */
	return 0 ;
}

/* Broadcast nodes list on named interface. */
static int
dobcnodes(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	register int i,j;

	for(i = 0 ; i < Nr_numiface ; i++)
		if(!strcmp(Nrifaces[i].iface->name,argv[1]))
			break ;
	if(i == Nr_numiface) {
		tprintf("Interface \"%s\" not found\n",argv[1]) ;
		return 1 ;
	}
		
	nr_bcnodes(i) ;
	return 0;
}

/* Set outbound node broadcast interval */
static int
donodetimer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc < 2){
		tprintf("Nodetimer %lu/%lu seconds\n",
			read_timer(&Nodetimer)/1000L,
			dur_timer(&Nodetimer)/1000L);
		return 0;
	}
	stop_timer(&Nodetimer) ;	/* in case it's already running */
	Nodetimer.func = (void (*)())donodetick;/* what to call on timeout */
	Nodetimer.arg = NULLCHAR;		/* dummy value */
	set_timer(&Nodetimer,atoi(argv[1])*1000L);	/* set timer duration */
	start_timer(&Nodetimer);		/* and fire it up */
	return 0;
}

void
donodetick()
{
	register int i ;

	for(i = 0 ; i < Nr_numiface ; i++)
		nr_bcnodes(i) ;

	/* Restart timer */
	start_timer(&Nodetimer) ;
}

/* Set timer for aging routes */
static int
doobsotimer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc < 2){
		tprintf("Obsotimer %lu/%lu seconds\n",
			read_timer(&Obsotimer)/1000L,
			dur_timer(&Obsotimer)/1000L);
		return 0;
	}
	stop_timer(&Obsotimer) ;	/* just in case it's already running */
	Obsotimer.func = (void (*)())doobsotick;/* what to call on timeout */
	Obsotimer.arg = NULLCHAR;		/* dummy value */
	set_timer(&Obsotimer,atoi(argv[1])*1000L);	/* set timer duration */
	start_timer(&Obsotimer);		/* and fire it up */
	return 0;
}


/* Go through the routing table, reducing the obsolescence count of
 * non-permanent routes, and purging them if the count reaches 0
 */
static void
doobsotick()
{
	register struct nrnbr_tab *np ;
	register struct nrroute_tab *rp, *rpnext ;
	register struct nr_bind *bp, *bpnext ;
	int i ;

	for(i = 0 ; i < NRNUMCHAINS ; i++) {
		for(rp = Nrroute_tab[i] ; rp != NULLNRRTAB ; rp = rpnext) {
			rpnext = rp->next ; 	/* save in case we free this route */
			for(bp = rp->routes ; bp != NULLNRBIND ; bp = bpnext) {
				bpnext = bp->next ;	/* in case we free this binding */
				if(bp->flags & NRB_PERMANENT)	/* don't age these */
					continue ;
				if(--bp->obsocnt == 0) {		/* time's up! */
					if(bp->next != NULLNRBIND)
						bp->next->prev = bp->prev ;
					if(bp->prev != NULLNRBIND)
						bp->prev->next = bp->next ;
					else
						rp->routes = bp->next ;
					rp->num_routes-- ;			/* one less binding */
					np = bp->via ;				/* find the neighbor */
					free((char *)bp) ;				/* now we can free the bind */
					/* Check to see if we can free the neighbor */
					if(--np->refcnt == 0) {
						if(np->next != NULLNTAB)
							np->next->prev = np->prev ;
						if(np->prev != NULLNTAB)
							np->prev->next = np->next ;
						else {
							Nrnbr_tab[nrhash(np->call)] = np->next ;
						}
						free((char *)np) ;	/* free the storage */
					}
				}
			}
			if(rp->num_routes == 0) {		/* did we free them all? */
				if(rp->next != NULLNRRTAB)
					rp->next->prev = rp->prev ;
				if(rp->prev != NULLNRRTAB)
					rp->prev->next = rp->next ;
				else
					Nrroute_tab[i] = rp->next ;

				free((char *)rp) ;
			}
		}
	}

	start_timer(&Obsotimer) ;
}


static struct cmds Nfcmds[] = {
	"add",	donfadd,	0, 3,
		"netrom nodefilter add <neighbor> <interface> [quality]",
	"drop",	donfdrop,	0, 3,
		"netrom nodefilter drop <neighbor> <interface>",
	"mode",	donfmode,	0, 0,	NULLCHAR,
	NULLCHAR,	NULLFP,	0, 0,
		"nodefilter subcommands: add drop mode",
} ;

/* nodefilter command multiplexer */
static int
donodefilter(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	if(argc < 2) {
		donfdump() ;
		return 0 ;
	}
	return subcmd(Nfcmds,argc,argv,p) ;
}

/* display a list of <callsign,interface> pairs from the filter
 * list.
 */
static int
donfdump()
{
	int i, column = 1 ;
	struct nrnf_tab *fp ;
	char buf[16] ;

	for(i = 0 ; i < NRNUMCHAINS ; i++)
		for(fp = Nrnf_tab[i] ; fp != NULLNRNFTAB ; fp = fp->next) {
			pax25(buf,fp->neighbor) ;
			tprintf("%-7s  %-8s  %-3d   ",
			 buf,Nrifaces[fp->iface].iface->name, fp->quality) ;
			if(column++ == 3) {
				if(tprintf("\n") == EOF)
					return 0;
				column = 1 ;
			}
		}

	if(column != 1)
		tprintf("\n") ;

	return 0 ;
}

/* add an entry to the filter table */
static int
donfadd(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	unsigned qual;
	char neighbor[AXALEN] ;
	register int i ;

	/* format callsign */
	if(setcall(neighbor,argv[1]) == -1) {
		tprintf("bad neighbor callsign\n") ;
		return -1 ;
	}

	/* find interface */
	for(i = 0 ; i < Nr_numiface ; i++)
		if(!strcmp(Nrifaces[i].iface->name,argv[2]))
			break ;
	if(i == Nr_numiface) {
		tprintf("Interface \"%s\" not found\n",argv[2]) ;
		return -1 ;
	}

	qual = Nrifaces[i].quality;	/* set default quality */

	if(argc >= 4)
		qual = atoi(argv[3]);

	return nr_nfadd(neighbor,i,qual) ;
}

/* drop an entry from the filter table */
static int
donfdrop(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	char neighbor[AXALEN] ;
	register int i ;

	/* format neighbor callsign */
	if(setcall(neighbor,argv[1]) == -1) {
		tprintf("bad neighbor callsign\n") ;
		return -1 ;
	}

	/* find interface */
	for(i = 0 ; i < Nr_numiface ; i++)
		if(!strcmp(Nrifaces[i].iface->name,argv[2]))
			break ;
	if(i == Nr_numiface) {
		tprintf("Interface \"%s\" not found\n",argv[2]) ;
		return -1 ;
	}

	return nr_nfdrop(neighbor,i) ;
}

/* nodefilter mode subcommand */
static int
donfmode(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	if(argc < 2) {
		tprintf("filter mode is ") ;
		switch(Nr_nfmode) {
			case NRNF_NOFILTER:
				tprintf("none\n") ;
				break ;
			case NRNF_ACCEPT:
				tprintf("accept\n") ;
				break ;
			case NRNF_REJECT:
				tprintf("reject\n") ;
				break ;
			default:
				tprintf("some strange, unknown value\n") ;
		}
		return 0 ;
	}
	
	switch(argv[1][0]) {
		case 'n':
		case 'N':
			Nr_nfmode = NRNF_NOFILTER ;
			break ;
		case 'a':
		case 'A':
			Nr_nfmode = NRNF_ACCEPT ;
			break ;
		case 'r':
		case 'R':
			Nr_nfmode = NRNF_REJECT ;
			break ;
		default:
			tprintf("modes are: none accept reject\n") ;
			return -1 ;
	}

	return 0 ;
}

/* netrom network packet time-to-live initializer */
static int
donrttl(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setshort(&Nr_ttl,"Time to live",argc,argv);
}

/* verbose route broadcast */
static int
donrverbose(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setbool(&Nr_verbose,"Verbose flag",argc,argv);
}
/* allow automatic derating of netrom routes on link failure */
static int
donrderate(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	extern int Nr_derate;

	return setbool(&Nr_derate,"Derate flag",argc,argv);
}

/* promiscuous acceptance of broadcasts */
static int
donrpromisc(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	extern int Nr_promisc;

	return setbool(&Nr_promisc,"Promiscuous flag",argc,argv);
}

/* Initiate a NET/ROM transport connection */
static int
donrconnect(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	char *np ;
	struct sockaddr_nr lsocket, fsocket;
	char alias[AXBUF];
	struct session *sp;

	/* Get a session descriptor */
	if((sp = newsession(argv[1],NRSESSION,0)) == NULLSESSION) {
		tprintf("Too many sessions\n") ;
		return 1 ;
	}

	if((sp->s = socket(AF_NETROM,SOCK_SEQPACKET,0)) == -1){
		tprintf("Can't create socket\n");
		keywait(NULLCHAR,1);
		freesession(sp);
		return 1;
	}
	lsocket.nr_family = AF_NETROM;
	/* Set up our local username, bind would use Mycall instead */
	memcpy(lsocket.nr_addr.user,Nr4user,AXALEN);
	/* Putting anything else than Mycall here will not work */
	memcpy(lsocket.nr_addr.node,Mycall,AXALEN);
	bind(sp->s,(char *)&lsocket,sizeof(struct sockaddr_nr));

	/* See if the requested destination could be an alias, and */
	/* find and use it if it is.  Otherwise assume it is an ax.25 */
	/* address. */
	
	if(putalias(alias,argv[1],0) != -1 &&
	    (np = find_nralias(alias)) != NULLCHAR) {
	    memcpy(fsocket.nr_addr.user,np,AXALEN) ;
	    memcpy(fsocket.nr_addr.node,np,AXALEN) ;
	} else {	/* parse ax25 callsign */
	    /* Only the user callsign of the remote station is never used by */
	    /* NET/ROM, but it is needed for the psocket() call. */
		setcall(fsocket.nr_addr.user,argv[1]);
		setcall(fsocket.nr_addr.node,argv[1]);
	}
	fsocket.nr_family = AF_NETROM;
	pax25(alias,fsocket.nr_addr.node);
	return tel_connect(sp, (char *)&fsocket, sizeof(struct sockaddr_nr));
}

/* Reset a net/rom connection abruptly */
static int
donrreset(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct nr4cb *cb ;

	cb = (struct nr4cb *)htol(argv[1]);
	if(!nr4valcb(cb)){
		tprintf(Notval);
		return 1;
	}
	reset_nr4(cb);
	return 0;
}

/* Force retransmission on a net/rom connection */

static int
donrkick(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct nr4cb *cb ;

	cb = (struct nr4cb *)htol(argv[1]);

	if(kick_nr4(cb) == -1) {
		tprintf(Notval);
		return 1;
	} else
		return 0;
}

/* netrom transport ACK delay timer */
static int
donracktime(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setlong(&Nr4acktime,"Ack delay time (ms)",argc,argv);
}

/* netrom transport choke timeout */
static int
donrchoketime(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setlong(&Nr4choketime,"Choke timeout (ms)",argc,argv);
}

/* netrom transport initial round trip time */

static int
donrirtt(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setlong(&Nr4irtt,"Initial RTT (ms)",argc,argv);
}

/* netrom transport receive queue length limit.  This is the */
/* threshhold at which we will CHOKE the sender. */

static int
donrqlimit(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setshort(&Nr4qlimit,"Queue limit (bytes)",argc,argv);
}

/* Display or change our NET/ROM username */
static int
donruser(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char buf[AXBUF];

	if(argc < 2){
		pax25(buf,Nr4user);
		tprintf("%s\n",buf);
		return 0;
	}
	if(setcall(Nr4user,argv[1]) == -1)
		return -1;
	Nr4user[ALEN] |= E;
	return 0;
}

/* netrom transport maximum window.  This is the largest send and */
/* receive window we may negotiate */

static int
donrwindow(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setshort(&Nr4window,"Window (frames)",argc,argv);
}

/* netrom transport maximum retries.  This is used in connect and */
/* disconnect attempts; I haven't decided what to do about actual */
/* data retries yet. */

static int
donrretries(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	return setshort(&Nr4retries,"Retry limit",argc,argv);
}

/* Display the status of NET/ROM connections */

static int
donrstatus(argc, argv,p)
int argc ;
char *argv[] ;
void *p;
{
	int i ;
	struct nr4cb *cb ;
	char luser[AXBUF], ruser[AXBUF], node[AXBUF] ;
	
	if(argc < 2) {
		tprintf("     &CB Snd-W Snd-Q Rcv-Q     LUser      RUser @Node     State\n");
		for(i = 0 ; i < NR4MAXCIRC ; i++) {
			if((cb = Nr4circuits[i].ccb) == NULLNR4CB)
				continue ;
			pax25(luser,cb->local.user) ;
			pax25(ruser,cb->remote.user) ;
			pax25(node,cb->remote.node) ;
			if(tprintf("%8lx   %3d %5d %5d %9s  %9s %-9s %s\n",
			 ptol(cb), cb->nbuffered, len_q(cb->txq),
			 len_p(cb->rxq), luser, ruser, node,
			 Nr4states[cb->state]) == EOF)
				break;
		}
		return 0 ;
	}

	cb = (struct nr4cb *)htol(argv[1]) ;
	if(!nr4valcb(cb)) {
		tprintf(Notval) ;
		return 1 ;
	}

	donrdump(cb) ;
	return 0 ;
}

/* Dump one control block */

void
donrdump(cb)
struct nr4cb *cb ;
{
	char luser[AXBUF], ruser[AXBUF], node[AXBUF] ;
	unsigned seq ;
	struct nr4txbuf *b ;
	struct timer *t ;

	pax25(luser,cb->local.user) ;
	pax25(ruser,cb->remote.user) ;
	pax25(node,cb->remote.node) ;

	tprintf("Local: %s %d/%d Remote: %s @ %s %d/%d State: %s\n",
		   luser, cb->mynum, cb->myid, ruser, node,
		   cb->yournum, cb->yourid, Nr4states[cb->state]) ;

	tprintf("Window: %-5u Rxpect: %-5u RxNext: %-5u RxQ: %-5d %s\n",
		   cb->window, uchar(cb->rxpected), uchar(cb->rxpastwin),
		   len_p(cb->rxq), cb->qfull ? "RxCHOKED" : "") ;

	tprintf(" Unack: %-5u Txpect: %-5u TxNext: %-5u TxQ: %-5d %s\n",
		   cb->nbuffered, uchar(cb->ackxpected), uchar(cb->nextosend),
		   len_q(cb->txq), cb->choked ? "TxCHOKED" : "") ;

	tprintf("TACK: ") ;
	if(run_timer(&cb->tack))
		tprintf("%lu", read_timer(&cb->tack)) ;
	else
		tprintf("stop") ;
	tprintf("/%lu ms; ", dur_timer(&cb->tack)) ;

	tprintf("TChoke: ") ;
	if(run_timer(&cb->tchoke))
		tprintf("%lu", read_timer(&cb->tchoke)) ;
	else
		tprintf("stop") ;
	tprintf("/%lu ms; ", dur_timer(&cb->tchoke)) ;

	tprintf("TCD: ") ;
	if(run_timer(&cb->tcd))
		tprintf("%lu", read_timer(&cb->tcd)) ;
	else
		tprintf("stop") ;
	tprintf("/%lu ms", dur_timer(&cb->tcd)) ;

	if(run_timer(&cb->tcd))
		tprintf("; Tries: %u\n", cb->cdtries) ;
	else
		tprintf("\n") ;

	tprintf("Backoff Level %u SRTT %ld ms Mean dev %ld ms\n",
		   cb->blevel, cb->srtt, cb->mdev) ;

	/* If we are connected and the send window is open, display */
	/* the status of all the buffers and their timers */
	
	if(cb->state == NR4STCON && cb->nextosend != cb->ackxpected) {

		tprintf("TxBuffers:  Seq  Size  Tries  Timer\n") ;

		for(seq = cb->ackxpected ;
			 nr4between(cb->ackxpected, seq, cb->nextosend) ;
			 seq = (seq + 1) & NR4SEQMASK) {

			b = &cb->txbufs[seq % cb->window] ;
			t = &b->tretry ;

			if(tprintf("            %3u   %3d  %5d  %lu/%lu\n",
			 seq, len_p(b->data), b->retries + 1,
			 read_timer(t), dur_timer(t))
			 == EOF)
				break;
		}

	}

}

/* netrom timers type - linear v exponential */
static
donrtype(argc,argv,p)
int argc ;
char *argv[] ;
void *p ;
{
	extern unsigned Nr_timertype;

	if(argc < 2) {
		tprintf("Netrom timer type is %s\n", Nr_timertype ? "linear" : "exponential" ) ;
		return 0 ;
	}
	
	switch(argv[1][0]) {
		case 'l':
		case 'L':
			Nr_timertype = 1 ;
			break ;
		case 'e':
		case 'E':
			Nr_timertype = 0 ;
			break ;
		default:
			tprintf("use: netrom timertype [linear|exponential]\n") ;
			return -1 ;
	}

	return 0 ;
}

static
dominquality(argc,argv,p)
int argc ;
char *argv[] ;
void *p ;
{
	unsigned val ;
	extern unsigned Nr_autofloor;

	if(argc < 2) {
		tprintf("%u\n", Nr_autofloor) ;
		return 0 ;
	}

	val = atoi(argv[1]) ;

	if(val == 0 || val > 255 ) {
		tprintf("The minimum acceptable quality must be 1 to 255\n") ;
		return 1 ;
	}
	
	Nr_autofloor = val ;

	return 0 ;
}
int
donrload(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	char buff[255];
	FILE *fn;
	time_t now, prev;
	long t1,t2;
	int quality,obso,i,j;
	char alias[12],dest[12],iface[12],neighbor[12],type[3],*ptr;
	
	time(&now);
	if((fn = fopen(Netromfile,READ_TEXT)) == NULLFILE){
		tprintf("Can't open netrom save file!\n");
		return 1;
	}

	fgets(buff,255,fn);	/* read the timestamp */
	if(feof(fn)){
		fclose(fn);
		return 1;
	}
	if((strncmp(buff,"time = ",7))!= 0){
		tprintf("Wrong node file content\n");
		fclose(fn);
		return 1;
	}
	sscanf(buff,"time =%ld",&prev);
	tprintf("now = %ld , prev = %ld\n",now,prev);
	if(prev >= now){
		tprintf("You traveled back in time!!\n");
		fclose(fn);
		return 1;
	}
	t1 = now - prev;
	t2 = dur_timer(&Obsotimer)/1000L;
	j = t1 / t2;		/* recalculate obsolete count */
	tprintf("%ld seconds are past ( %d obsolete scans)\n",t1,j);

	fgets(buff,255,fn);
	while(!feof(fn)){
		if((ptr = strchr(buff,':')) == 0){
			sscanf(buff,"%s%s%i%i%s%s"
				,dest,type,&quality,&obso,iface,neighbor);
			alias[0] = '\0';
		} else {	
			*ptr = ' ';
			sscanf(buff,"%s%s%s%i%i%s%s"
				,alias,dest,type,&quality,&obso,iface,neighbor);
		}
		/* find interface */
		for(i = 0 ; i < Nr_numiface ; i++)
			if(!strcmp(Nrifaces[i].iface->name,iface))
				break ;
		if(i == Nr_numiface) {
			tprintf("Interface \"%s\" not found\n",iface) ;
		}
	
		/* get and check quality value */
		if(quality  > 255) {
			tprintf("maximum route quality is 255\n") ;
		}

/*		nr_routeadd(alias,dest,i,quality,neighbor,1,0) ; */
		fgets(buff,255,fn);
	}
	fclose(fn);
	return 0;
}

int
donrsave(argc,argv,p)
int argc ;
char *argv[] ;
void *p;
{
	register struct nrroute_tab *rp ;
	register struct nr_bind *bp ;
	register struct nrnbr_tab *np ;
	char dest[AXALEN] ;
	char neighbor[AXBUF] ;
	register int i;
	char buf[16] ;
	char *cp ;
	FILE *fn;
	time_t now;
	
#ifdef __TURBOC__
	if((fn = fopen(Netromfile,"wt+")) == NULLFILE){
#else
	if((fn = fopen(Netromfile,"w+")) == NULLFILE){
#endif
		tprintf("Can't write netrom save file!\n");
		return 1;
	}
	time(&now);
	fprintf(fn,"time = %ld\n",now);
	for(i = 0 ; i < NRNUMCHAINS ; i++){
		for(rp = Nrroute_tab[i] ; rp != NULLNRRTAB ; rp = rp->next){
			strcpy(buf,rp->alias) ;
			/* remove trailing spaces */
			if((cp = strchr(buf,' ')) == NULLCHAR)
				cp = &buf[strlen(buf)] ;
			if(cp != buf)		/* don't include colon for null alias */
				*cp++ = ':' ;
			for(bp = rp->routes; bp != NULLNRBIND; bp = bp->next) {
				pax25(cp,rp->call) ;
				fprintf(fn,"%-16s  ",buf) ;
				np = bp->via ;
				if(fprintf(fn,"%1s %3d  %3d  %-8s  %s\n",
					  (bp->flags & NRB_PERMANENT ? "P" :
					  bp->flags & NRB_RECORDED ? "R" : "X"),
					  bp->quality,bp->obsocnt,
					  Nrifaces[np->iface].iface->name,
					  pax25(neighbor,np->call)) == EOF)
				   break;
			}
		}
	}
	fclose(fn);
	return 0;
}
