#include "global.h"
#include "mbuf.h"
#include "timer.h"
#include "iface.h"
#include "cmdparse.h"
#include "netuser.h"
#include "socket.h"
#include "rspf.h"

int Rspfownmode = -1;
static int dointerface __ARGS((int argc,char *argv[],void *p));
static int domessage __ARGS((int argc,char *argv[],void *p));
static int domaxping __ARGS((int argc,char *argv[],void *p));
static int domode __ARGS((int argc,char *argv[],void *p));
static int dorrhtimer __ARGS((int argc,char *argv[],void *p));
static int dotimer __ARGS((int argc,char *argv[],void *p));
static int doroutes __ARGS((int argc,char *argv[],void *p));
static int dostatus __ARGS((int argc,char *argv[],void *p));
static int dosuspect __ARGS((int argc,char *argv[],void *p));
static struct timer rrhtimer, rspftimer;

static struct cmds Rspfcmds[] = {
	"interface",	dointerface,	0,	0,	NULLCHAR,
	"message",	domessage,	0,	0,	NULLCHAR,
	"maxping",	domaxping,	0,	0,	NULLCHAR,
	"mode",		domode,		0,	0,	NULLCHAR,
	"rrhtimer",	dorrhtimer,	0,	0,	NULLCHAR,
	"routes",	doroutes,	0,	0,	NULLCHAR,
	"status",	dostatus,	0,	0,	NULLCHAR,
	"suspecttimer",	dosuspect,	0,	0,	NULLCHAR,
	"timer",	dotimer,	0,	0,	NULLCHAR,
	NULLCHAR
};

int
dorspf(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Rspfcmds,argc,argv,p);
}

/* The suspect timer controls how often old links expire. When a link has
 * expired, we try to renew its entry by various methods.
 */
static int
dosuspect(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(Rspfifaces == NULLRIFACE){
		tprintf("RSPF is not active - define interface first.\n");
		return 0;
	}
	if(argc < 2){
		tprintf("Suspect timer: %lu/%lu seconds\n",
			read_timer(&Susptimer)/1000,
			dur_timer(&Susptimer)/1000);
		return 0;
	}
	Susptimer.func = rspfsuspect; /* what to call on timeout */
	Susptimer.arg = NULL;			/* dummy value */
	set_timer(&Susptimer,atol(argv[1])*1000L); /* set timer duration */
	start_timer(&Susptimer);		/* and fire it up */
	return 0;
}

/* The RRH timer controls the interval between Router-To-Router Hello
 * messages. These messages announce that your station is live and well
 * and that you are willing to exchange RSPF routing updates.
 */
static int
dorrhtimer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(Rspfifaces == NULLRIFACE){
		tprintf("RSPF is not active - define interface first.\n");
		return 0;
	}
	if(argc < 2){
		tprintf("RRH timer: %lu/%lu seconds\n",
			read_timer(&rrhtimer)/1000,
			dur_timer(&rrhtimer)/1000);
		return 0;
	}
	rrhtimer.func = rspfevent; /* what to call on timeout */
	rrhtimer.arg = (void *) &rrhtimer;
	set_timer(&rrhtimer,atol(argv[1])*1000L); /* set timer duration */
	start_timer(&rrhtimer);		/* and fire it up */
	return 0;
}

/* This timer controls the interval between the RSPF routing updates. */
static int
dotimer(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(Rspfifaces == NULLRIFACE){
		tprintf("RSPF is not active - define interface first.\n");
		return 0;
	}
	if(argc < 2){
		tprintf("RSPF update timer: %lu/%lu seconds\n",
			read_timer(&rspftimer)/1000,
			dur_timer(&rspftimer)/1000);
		return 0;
	}
	rspftimer.func = rspfevent; /* what to call on timeout */
	rspftimer.arg = (void *) &rspftimer;
	set_timer(&rspftimer,atol(argv[1])*1000L); /* set timer duration */
	start_timer(&rspftimer);		/* and fire it up */
	return 0;
}

/* Called when either the RRH timer, the Update timer or the Suspect timer
 * expires.
 */
void
rspfevent(t)
void *t;
{
     int cmd;
     struct mbuf *bp;
     struct rspfadj *adj = NULLADJ;
     struct timer *tp;
     tp = (struct timer *) t;
     if(tp == &rrhtimer) {
	  cmd = RSPFE_RRH;
	  start_timer(tp);
     }
     else if(tp == &rspftimer) {
	  cmd = RSPFE_UPDATE;
	  start_timer(tp);
     }
     else {
	  for(adj = Adjs; adj != NULLADJ; adj = adj->next)
	       if(&adj->timer == tp)
		    break;
	  if(adj == NULLADJ)
	       return;
	  cmd = RSPFE_CHECK;
     }
     bp = ambufw(1+sizeof(int32));
     *bp->data = cmd;
     memcpy(bp->data + 1,&adj,sizeof(adj));
     bp->cnt = bp->size;
     enqueue(&Rspfinq,bp);
}

static int
domessage(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc > 2) {
		tputs("Usage: rspf message \"<your message>\"\n");
		return 0;
	}

	if(argc < 2) {
		if(Rrh_message != NULLCHAR)
			tputs(Rrh_message);
	}
	else {
		if(Rrh_message != NULLCHAR){
			free(Rrh_message);
			Rrh_message = NULLCHAR;	/* reset the pointer */
		}
		if(!strlen(argv[1]))
			return 0;		/* clearing the buffer */
		Rrh_message = mallocw(strlen(argv[1])+5);/* allow for EOL */
		strcpy(Rrh_message, argv[1]);
		strcat(Rrh_message, INET_EOL);	/* add the EOL char */
	}
	return 0;
}

static int
domaxping(argc,argv,p)
int argc;
char *argv[];
void *p;
{
     return setshort(&Rspfpingmax,"Max failed pings before deleting adjacency",
		     argc,argv);
}

static int
domode(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(argc < 2) {
	tputs("RSPF preferred mode is ");
	if(Rspfownmode == -1)
	    tputs("not set.\n");
	else
	    tprintf("%s.\n",(Rspfownmode & CONNECT_MODE) ? "VC mode" :
		   "Datagram mode");
	return 0;
    }
    switch(*argv[1]){
    case 'v':
    case 'c':
    case 'V':
    case 'C':
	Rspfownmode = CONNECT_MODE;
	break;
    case 'd':
    case 'D':
	Rspfownmode = DATAGRAM_MODE;
	break;
    case 'n':
    case 'N':
	Rspfownmode = -1;
	break;
    default:
	tputs("Usage: rspf mode [vc | datagram | none]\n");
	return 1;
    }
    return 0;
}

static int
dointerface(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct rspfiface *riface;
    struct iface *iface;
    struct mbuf *bp;
    int h,q;
    if(argc < 2){
	tputs("Iface    Quality    Horizon\n");
	for(riface = Rspfifaces; riface != NULLRIFACE; riface = riface->next)
	    tprintf("%-9s%-11d%-11d\n",riface->iface->name,riface->quality,
		   riface->horizon);
	return 0;
    }
    if(argc != 4){
	tputs("Usage: rspf interface <name> <quality> <horizon>\n");
	return 1;
    }
    if((iface = if_lookup(argv[1])) == NULLIF){
	tputs("No such interface.\n");
	return 1;
    }
    if(iface->broadcast == 0){
	tprintf("Broadcast address for interface %s not set\n",argv[1]);
	return 1;
    }
    q = atoi(argv[2]);
    if(q < 1 || q > 127){
	tputs("Quality must be between 1 and 127\n");
	return 1;
    }
    h = atoi(argv[3]);
    if(h < 1 || h > 255){
	tputs("Horizon must be between 1 and 255\n");
	return 1;
    }
    riface = (struct rspfiface *)callocw(1,sizeof(struct rspfiface));
    riface->iface = iface;
    riface->quality = q;
    riface->horizon = h;
    riface->next = Rspfifaces;
    if(Rspfifaces == NULLRIFACE)
	 newproc("RSPF",2048,rspfmain,0,NULL,NULL,0);
    Rspfifaces = riface;
    bp = ambufw(1+sizeof(int32));
    *bp->data = RSPFE_RRH;		/* Send an RRH immediately */
    memcpy(bp->data + 1,&riface,sizeof(riface));
    bp->cnt = bp->size;
    enqueue(&Rspfinq,bp);
    return 0;
}

/* Display accumulated routing updates */
static int
doroutes(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct mbuf *bp;
    struct rspfrouter *rr;
    if(Rspfifaces == NULLRIFACE){
	tprintf("RSPF is not active - define interface first.\n");
	return 0;
    }
    bp = makeownupdate(INADDR_ANY,0);
    if(bp == NULLBUF && Rspfrouters == NULLRROUTER) {
	 tputs("No routing information is available.\n");
	 return 0;
    }
    if(bp != NULLBUF) {
	tputs("      Local routing update:\n");
	rspfnodedump(NULLFILE,&bp,0);
	tputc('\n');
    }
    for(rr = Rspfrouters; rr != NULLRROUTER; rr = rr->next) {
	tprintf("      Time since receipt: %s",tformat(secclock() - rr->time));
	if(rr->subseq != 0)
	     tprintf("  Last subseq: %u",uchar(rr->subseq));
	if(rr->sent)
	     tputs("  Propagated");
	tputc('\n');
	if(rr->data != NULLBUF) {
	     dup_p(&bp,rr->data,0,len_p(rr->data));
	     rspfnodedump(NULLFILE,&bp,0);
	     tputc('\n');
	}
    }
    return 0;
}

static int
dostatus(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct rspfreasm *re;
	struct rspfadj *adj;
	struct mbuf *bp;
	union rspf rspf;
	if(Rspfifaces == NULLRIFACE){
		tprintf("RSPF is not active - define interface first.\n");
		return 0;
	}
	tprintf("Bad checksum %u  Bad version %u  Not RSPF interface %u\n",
		Rspf_stat.badcsum,Rspf_stat.badvers,Rspf_stat.norspfiface);
	tprintf("RRH in %u  RRH out %u  Update in %u  Update out %u\n",
		Rspf_stat.rrhin,Rspf_stat.rrhout,Rspf_stat.updatein,
		Rspf_stat.updateout);
	tprintf("Non-adjacency update %u  Old node report %u  Polls sent %u\n",
		Rspf_stat.noadjupdate,Rspf_stat.oldreport,Rspf_stat.outpolls);
	if(Adjs == NULLADJ)
		return 0;
	tputs("Addr            Cost    Seq    Heard    Timer     TOS    State\n");
	for(adj = Adjs; adj != NULLADJ; adj = adj->next) {
		tprintf("%-15s %4u  %5u   %6lu ", inet_ntoa(adj->addr),
			uchar(adj->cost),adj->seq,adj->heard);
		if(run_timer(&adj->timer))
		     tprintf("%5lu/%-5lu",
			read_timer(&adj->timer)/1000L ,dur_timer(&adj->timer)/1000L);
		else
		     tprintf("%11s","");
		tprintf("  %3u    ", uchar(adj->tos));
		switch(adj->state) {
		case RSPF_TENTATIVE:
			tputs("Tentative");
			break;
		case RSPF_OK:
			tputs("OK");
			break;
		case RSPF_SUSPECT:
			tputs("Suspect");
			break;
		case RSPF_BAD:
			tputs("Bad");
			break;
		default:
			tputs("Unknown");
			break;
		}
		tputc('\n');
	}
	if(run_timer(&Rspfreasmt)) {
	     tprintf("Reassembly timer running: %lu/%lu seconds\n",
		     read_timer(&Rspfreasmt)/1000L, dur_timer(&Rspfreasmt)/1000L);
	}
	if(Rspfreasmq != NULLRREASM)
	     tputs("Reassembly fragments:\n");
	for(re = Rspfreasmq; re != NULLRREASM; re = re->next) {
	     tprintf("src %s time since last frag %s",inet_ntoa(re->addr),
		     tformat((secclock() - re->time)));
	     if(dup_p(&bp,re->data,0,RSPFPKTLEN) == RSPFPKTLEN &&
		ntohrspf(&rspf,&bp) != -1)
		  tprintf(" frag count %u/%u\n",len_q(re->data),
			  rspf.pkthdr.fragtot);
	     else {
		  tputc('\n');
		  free_p(bp);
		  continue;
	     }
	}
	return 0;
}
