/* AX25 control commands
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#include "ax25.h"
#include "lapb.h"
#include "cmdparse.h"
#include "socket.h"
#include "mailbox.h"
#include "session.h"
#include "tty.h"
#include "nr4.h"
#include "commands.h"

static int axheard __ARGS((struct iface *ifp));
static void axflush __ARGS((struct iface *ifp));
static int doaxflush __ARGS((int argc,char *argv[],void *p));
static int doaxirtt __ARGS((int argc,char *argv[],void *p));
static int doaxkick __ARGS((int argc,char *argv[],void *p));
static int doaxreset __ARGS((int argc,char *argv[],void *p));
static int doaxroute __ARGS((int argc,char *argv[],void *p));
static int doaxstat __ARGS((int argc,char *argv[],void *p));
static int doaxwindow __ARGS((int argc,char *argv[],void *p));
static int doblimit __ARGS((int argc,char *argv[],void *p));
static int dodigipeat __ARGS((int argc,char *argv[],void *p));
static int domaxframe __ARGS((int argc,char *argv[],void *p));
static int domycall __ARGS((int argc,char *argv[],void *p));
static int don2 __ARGS((int argc,char *argv[],void *p));
static int dopaclen __ARGS((int argc,char *argv[],void *p));
static int dopthresh __ARGS((int argc,char *argv[],void *p));
static int dot3 __ARGS((int argc,char *argv[],void *p));
static int doversion __ARGS((int argc,char *argv[],void *p));

char *Ax25states[] = {
	"",
	"Disconnected",
	"Listening",
	"Conn pending",
	"Disc pending",
	"Connected",
	"Recovery",
};

/* Ascii explanations for the disconnect reasons listed in lapb.h under
 * "reason" in ax25_cb
 */
char *Axreasons[] = {
	"Normal",
	"DM received",
	"Timeout"
};

static struct cmds Axcmds[] = {
	"blimit",	doblimit,	0, 0, NULLCHAR,
	"digipeat",	dodigipeat,	0, 0, NULLCHAR,
	"flush",	doaxflush,	0, 0, NULLCHAR,
	"heard",	doaxheard,	0, 0, NULLCHAR,
	"irtt",		doaxirtt,	0, 0, NULLCHAR,
	"kick",		doaxkick,	0, 2, "ax25 kick <axcb>",
	"maxframe",	domaxframe,	0, 0, NULLCHAR,
	"mycall",	domycall,	0, 0, NULLCHAR,
	"paclen",	dopaclen,	0, 0, NULLCHAR,
	"pthresh",	dopthresh,	0, 0, NULLCHAR,
	"reset",	doaxreset,	0, 2, "ax25 reset <axcb>",
	"retry",	don2,		0, 0, NULLCHAR,
	"route",	doaxroute,	0, 0, NULLCHAR,
	"status",	doaxstat,	0, 0, NULLCHAR,
	"t3",		dot3,		0, 0, NULLCHAR,
	"version",	doversion,	0, 0, NULLCHAR,
	"window",	doaxwindow,	0, 0, NULLCHAR,
	NULLCHAR,
};
/* Multiplexer for top-level ax25 command */
int
doax25(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Axcmds,argc,argv,p);
}

int
doaxheard(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp;

	if(argc > 1){
		if((ifp = if_lookup(argv[1])) == NULLIF){
			tprintf("Interface %s unknown\n",argv[1]);
			return 1;
		}
		if(ifp->output != ax_output){
			tprintf("Interface %s not AX.25\n",argv[1]);
			return 1;
		}
		axheard(ifp);
		return 0;
	}
	for(ifp = Ifaces;ifp != NULLIF;ifp = ifp->next){
		if(ifp->output != ax_output)
			continue;	/* Not an ax.25 interface */
		if(axheard(ifp) == EOF)
			break;
	}
	return 0;
}
static int
axheard(ifp)
struct iface *ifp;
{
	int i;
	struct lq *lp;
	char tmp[AXBUF];

	if(ifp->hwaddr == NULLCHAR)
		return 0;
	tprintf("%s:\n",ifp->name);
	tprintf("Station   Time since last heard   Pkts recvd\n");
	tprintf("%-9s %-21s   %lu\n",pax25(tmp,ifp->hwaddr),
	 tformat(secclock() - ifp->lastsent),ifp->rawsndcnt);
	for(i=0;i<NHASH;i++){
		for(lp = Lq[i];lp != NULLLQ;lp = lp->next){
			if(lp->iface != ifp)
				continue;
			if(tprintf("%-9s %-21s   %lu\n",pax25(tmp,lp->addr),
			 tformat(secclock() - lp->time),lp->currxcnt) == EOF)
				return EOF;
		}
	}
	return 0;
}
static int
doaxflush(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp;

	for(ifp = Ifaces;ifp != NULLIF;ifp = ifp->next){
		if(ifp->output != ax_output)
			continue;	/* Not an ax.25 interface */
		axflush(ifp);
	}
	return 0;
}
static void
axflush(ifp)
struct iface *ifp;
{
	int i;
	struct lq *lp,*lp1;

	ifp->rawsndcnt = 0;
	for(i=0;i<NHASH;i++){
		for(lp = Lq[i];lp != NULLLQ;lp = lp1){
			lp1 = lp->next;
			free(lp->addr);
			free((char *)lp);
		}
		Lq[i] = NULLLQ;
	}
}

static
doaxreset(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct ax25_cb *axp;

	axp = (struct ax25_cb *)ltop(htol(argv[1]));
	if(!ax25val(axp)){
		tprintf(Notval);
		return 1;
	}
	reset_ax25(axp);
	return 0;
}

/* Display AX.25 link level control blocks */
static
doaxstat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register int i;
	register struct ax25_cb *axp;
	char tmp[AXBUF];

	if(argc < 2){
		tprintf("    &AXB Snd-Q   Rcv-Q   Remote    State\n");
		for(i=0;i<NHASH;i++){
			for(axp = Ax25_cb[i];axp != NULLAX25; axp = axp->next){

				if(tprintf("%8lx %-8d%-8d%-10s%s\n",
					ptol(axp),
					len_q(axp->txq),len_p(axp->rxq),
					pax25(tmp,axp->remote),
					Ax25states[axp->state]) == EOF)
						return 0;
			}
		}
		return 0;
	}
	axp = (struct ax25_cb *)ltop(htol(argv[1]));
	if(!ax25val(axp)){
		tprintf(Notval);
		return 1;
	}
	st_ax25(axp);
	return 0;
}
/* Dump one control block */
void
st_ax25(axp)
register struct ax25_cb *axp;
{
	char tmp[AXBUF];

	if(axp == NULLAX25)
		return;
	tprintf("    &AXB Remote   RB V(S) V(R) Unack P Retry State\n");

	tprintf("%8lx %-9s%c%c",ptol(axp),pax25(tmp,axp->remote),
	 axp->flags.rejsent ? 'R' : ' ',
	 axp->flags.remotebusy ? 'B' : ' ');
	tprintf(" %4d %4d",axp->vs,axp->vr);
	tprintf(" %02u/%02u %u",axp->unack,axp->maxframe,axp->proto);
	tprintf(" %02u/%02u",axp->retries,axp->n2);
	tprintf(" %s\n",Ax25states[axp->state]);

	tprintf("srtt = %lu mdev = %lu ",axp->srt,axp->mdev);
	tprintf("T1: ");
	if(run_timer(&axp->t1))
		tprintf("%lu",read_timer(&axp->t1));
	else
		tprintf("stop");
	tprintf("/%lu ms; ",dur_timer(&axp->t1));

	tprintf("T3: ");
	if(run_timer(&axp->t3))
		tprintf("%lu",read_timer(&axp->t3));
	else
		tprintf("stop");
	tprintf("/%lu ms\n",dur_timer(&axp->t3));

}

/* Display or change our AX.25 address */
static
domycall(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char tmp[AXBUF];

	if(argc < 2){
		tprintf("%s\n",pax25(tmp,Mycall));
		return 0;
	}
	if(setcall(Mycall,argv[1]) == -1)
		return -1;
	return 0;
}

/* Control AX.25 digipeating */
static
dodigipeat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Digipeat,"Digipeat",argc,argv);
}
/* Set limit on retransmission backoff */
static
doblimit(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setlong(&Blimit,"blimit",argc,argv);
}
static
doversion(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Axversion,"AX25 version",argc,argv);
}

static
doaxirtt(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setlong(&Axirtt,"Initial RTT (ms)",argc,argv);
}

/* Set idle timer */
static
dot3(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setlong(&T3init,"Idle poll timer (ms)",argc,argv);
}

/* Set retry limit count */
static
don2(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&N2,"Retry limit",argc,argv);
}
/* Force a retransmission */
static
doaxkick(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct ax25_cb *axp;

	axp = (struct ax25_cb *)ltop(htol(argv[1]));
	if(!ax25val(axp)){
		tprintf(Notval);
		return 1;
	}
	kick_ax25(axp);
	return 0;
}
/* Set maximum number of frames that will be allowed in flight */
static
domaxframe(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Maxframe,"Window size (frames)",argc,argv);
}

/* Set maximum length of I-frame data field */
static
dopaclen(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Paclen,"Max frame length (bytes)",argc,argv);
}
/* Set size of I-frame above which polls will be sent after a timeout */
static
dopthresh(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Pthresh,"Poll threshold (bytes)",argc,argv);
}

/* Set high water mark on receive queue that triggers RNR */
static
doaxwindow(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Axwindow,"AX25 receive window (bytes)",argc,argv);
}
/* End of ax25 subcommands */

/* Initiate interactive AX.25 connect to remote station */
int
doconnect(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct sockaddr_ax fsocket;
	struct session *sp;
	int ndigis,i;
	char digis[MAXDIGIS][AXALEN];
	char target[AXALEN];

	/* If digipeaters are given, put them in the routing table */
	if(argc > 3){
		setcall(target,argv[2]);
		ndigis = argc - 3;
		if(ndigis > MAXDIGIS){
			tprintf("Too many digipeaters\n");
			return 1;
		}
		for(i=0;i<ndigis;i++){
			if(setcall(digis[i],argv[i+3]) == -1){
				tprintf("Bad digipeater %s\n",argv[i+3]);
				return 1;
			}
		}
		if(ax_add(target,AX_LOCAL,digis,ndigis) == NULLAXR){
			tprintf("Route add failed\n");
			return 1;
		}
	}
	/* Allocate a session descriptor */
	if((sp = newsession(argv[2],AX25TNC)) == NULLSESSION){
		tprintf("Too many sessions\n");
		return 1;
	}
	if((sp->s = socket(AF_AX25,SOCK_STREAM,0)) == -1){
		tprintf("Can't create socket\n");
		freesession(sp);
		keywait(NULLCHAR,1);
		return 1;
	}
	fsocket.sax_family = AF_AX25;
	setcall(fsocket.ax25_addr,argv[2]);
	strncpy(fsocket.iface,argv[1],ILEN);
	return tel_connect(sp, (char *)&fsocket, sizeof(struct sockaddr_ax));
}

/* Display and modify AX.25 routing table */
static int
doaxroute(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char tmp[AXBUF];
	int i,j,ndigis;
	register struct ax_route *axr;
	char target[AXALEN],digis[MAXDIGIS][AXALEN];

	if(argc < 2){
		tprintf("Target    Type   Digipeaters\n");
		for(i=0;i<NHASH;i++){
			for(axr = Ax_routes[i];axr != NULLAXR;axr = axr->next){
				tprintf("%-10s%-6s",pax25(tmp,axr->target),
				 axr->type == AX_LOCAL ? "Local":"Auto");
				for(j=0;j<axr->ndigis;j++){
					tprintf(" %s",pax25(tmp,axr->digis[j]));
				}
				if(tprintf("\n") == EOF)
					return 0;
			}
		}
		return 0;
	}
	if(argc < 3){
		tprintf("Usage: ax25 route add <target> [digis...]\n");
		tprintf("       ax25 route drop <target>\n");
		return 1;
	}
	if(setcall(target,argv[2]) == -1){
		tprintf("Bad target %s\n",argv[2]);
		return 1;
	}
	switch(argv[1][0]){
	case 'a':	/* Add route */
		if(argc < 3){
			tprintf("Usage: ax25 route add <target> [digis...]\n");
			return 1;
		}
		ndigis = argc - 3;
		if(ndigis > MAXDIGIS){
			tprintf("Too many digipeaters\n");
			return 1;
		}
		for(i=0;i<ndigis;i++){
			if(setcall(digis[i],argv[i+3]) == -1){
				tprintf("Bad digipeater %s\n",argv[i+3]);
				return 1;
			}
		}
		if(ax_add(target,AX_LOCAL,digis,ndigis) == NULLAXR){
			tprintf("Failed\n");
			return 1;
		}
		break;
	case 'd':	/* Drop route */
		if(ax_drop(target) == -1){
			tprintf("Not in table\n");
			return 1;
		}
		break;
	default:
		tprintf("Unknown command %s\n",argv[1]);
		return 1;
	}
	return 0;
}
