/* Vestigial AX.25 link layer, understands only UI frames */

#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "arp.h"
#include "slip.h"
#include "ax25.h"
#include <ctype.h>

#ifdef	TRACE
#include "trace.h"
#endif

/* AX.25 broadcast address: "QST-0" in shifted ascii */
struct ax25_addr ax25_bdcst = {
	'Q'<<1, 'S'<<1, 'T'<<1, ' '<<1, ' '<<1, ' '<<1,
	('0'<<1) | E,
};
struct ax25_addr mycall;
int digipeat;		/* Controls digipeating */

/* Send IP datagrams in AX.25 UI frames using ARP */
int
ax_send(bp,interface,gateway,precedence,delay,throughput,reliability)
struct mbuf *bp;
struct interface *interface;
int32 gateway;
char precedence;
char delay;
char throughput;
char reliability;
{
	char *hw_addr,*res_arp();

	hw_addr = res_arp(interface,ARP_AX25,gateway,bp);
	if(hw_addr != NULLCHAR)
		(*interface->output)(interface,(struct ax25_addr *)hw_addr,
			interface->hwaddr,PID_IP,bp);
}
/* Send a raw packet to the KISS TNC using AX.25 link header.
 * Note that the calling order here must match ec_output
 * since ARP also uses it.
 */
kiss_output(interface,dest,src,pid,bp)
struct interface *interface;
struct ax25_addr *dest;	/* Destination AX.25 address (7 bytes, shifted) */
struct ax25_addr *src;	/* Source AX.25 address (7 bytes, shifted) */
char pid;		/* Protocol ID */
struct mbuf *bp;	/* Data field (follows PID) */
{
	register struct mbuf *hbp;
	struct mbuf *ax_encode();
	struct slip *sp;

	if((bp = ax_encode(dest,src,pid,bp)) == NULLBUF)
		return;
#ifdef	TRACE
	if(trace & TRACE_AX25){
		printf("%s sent:\r\n",interface->name);
		if((trace & TRACE_HDR) > 1)
			ax25_dump(bp);
		if(trace & TRACE_DUMP)
			hexdump(bp);
		if(trace & TRACE_ASCII)
			asciidump(bp);
		fflush(stdout);
	}
#endif
	/* Put type field for KISS TNC on front */
	if((hbp = alloc_mbuf(1)) == NULLBUF){
		free_p(bp);
		return;
	}
	*hbp->data = 0;
	hbp->cnt = 1;
	hbp->next = bp;
	slipq(interface->dev,hbp);
}

/* Put a AX.25 header on the front of a packet */
struct mbuf *
ax_encode(dest,src,pid,bp)
struct ax25_addr *dest;	/* Destination AX.25 address (7 bytes, shifted) */
struct ax25_addr *src;	/* Source AX.25 address (7 bytes, shifted) */
char pid;		/* Protocol ID */
struct mbuf *bp;	/* Data field (follows PID) */
{
	register struct ax25_addr *ap;
	struct mbuf *abp;
	char *cp;
	int ndigi;
	int16 hdr_len;

	/* Determine length of dest addr */
	for(ndigi = 0,ap = dest; (ap->ssid & E) == 0;
			ap = (struct ax25_addr *) ((char *)ap + AXALEN))
		ndigi++;

	/* Compute header length:
	 * 2 AX.25 address fields for source and dest +
	 * "ndigi" AX.25 address field(s) for digipeaters +
	 * 2 bytes for control and PID fields
	 */

	hdr_len = (2 + ndigi)*AXALEN + 2;

	/* Create AX.25 link level header */
	if((abp = alloc_mbuf(hdr_len)) == NULLBUF){
		free_p(bp);
		return NULLBUF;
	}
	abp->cnt = hdr_len;

	/* Now fill it in */

	ap = (struct ax25_addr *)(abp->data);

	bcopy((char *)dest,(char *)ap,AXALEN);
	ap->ssid &= ~E;
	ap = (struct ax25_addr *) ((char *)ap + AXALEN);
	dest = (struct ax25_addr *) ((char *)dest + AXALEN);

	bcopy((char *)src,(char *)ap,AXALEN);
	ap->ssid &= ~E;
	cp = (char *)ap;	/* get pointer to last address (source) */
	ap = (struct ax25_addr *) ((char *)ap + AXALEN);

	while(ndigi-- != 0) {
		bcopy((char *)dest,(char *)ap,AXALEN);
		dest = (struct ax25_addr *) ((char *)dest + AXALEN);
		cp = (char *)ap;
		ap = (struct ax25_addr *) ((char *)ap + AXALEN);
	}
	((struct ax25_addr *)cp)->ssid |= E;	/* Mark end of address field */	

	cp = (char *)ap;	/* Point to first byte past address field */
	*cp++ = UI;
	*cp++ = pid;

	abp->next = bp;		/* Link in data field */
	return abp;
}
/* Process incoming KISS TNC frame */
kiss_recv(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	pullup(&bp,NULLCHAR,1);	/* remove KISS TNC type field */
	if(bp != NULLBUF)
		ax_recv(interface,bp);
}
/* Process incoming AX.25 packets.
 * After optional tracing, the address field is examined. If it is
 * directed to us as a digipeater, repeat it.  If it is addressed to
 * us or to QST-0, kick it upstairs depending on the protocol ID.
 */
int
ax_recv(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	void arp_input(),ip_route();
	struct ax25_addr *ap,*ap1;
	char pid,multicast,ours,*control,*cbyte();
	int addrsize;
	struct mbuf *hbp;

#ifdef	TRACE
	if(trace & TRACE_AX25){
		printf("%s recv:\r\n",interface->name);
		if((trace & TRACE_HDR) > 1)
			ax25_dump(bp);
		if(trace & TRACE_DUMP)
			hexdump(bp);
		if(trace & TRACE_ASCII)
			asciidump(bp);
		fflush(stdout);
	}
#endif
	control = cbyte(bp);		/* control -> control byte */

	ap = (struct ax25_addr *)bp->data;	/* -> address field */
	addrsize = control - (char *)ap;	/* # bytes in address field */
	/* Check for either a missing control byte or a residual length
	 * address field
	 */
	if(control == NULL || addrsize % AXALEN != 0){
		free_p(bp);
		return;
	}
	addrsize /= AXALEN; /* # addresses in address field */
	/* Check for invalid address field (too short or odd length) */	
	if(addrsize < 2) {
		free_p(bp);
		return;
	}
	/* Rescan, looking for our call in the repeater fields, if any.
	 * Repeat appropriate packets.
	 */
	/* for(ap1 = &ap[2]; ap1 < &ap[addrsize]; ap1++){ */

	for(ap1 = (struct ax25_addr *) ((char *)ap + 2*AXALEN);
	    ap1 < (struct ax25_addr *) ((char *)ap + addrsize*AXALEN);
	    ap1 = (struct ax25_addr *) ((char *)ap1 + AXALEN)) {

		if((ap1->ssid & REPEATED) == 0){
			/* Check if packet is directed to us as a digipeater */
			if(digipeat && addreq(ap1,&mycall)){
				/* Yes, kick it back out */
				ap1->ssid |= REPEATED;
				/* Put type field for KISS TNC on front */
				if((hbp = alloc_mbuf(1)) == NULLBUF){
					free_p(bp);
					return;
				}
				*hbp->data = 0;
				hbp->cnt = 1;
				hbp->next = bp;
				slipq(interface->dev,hbp);
			} else {
				/* Addressed to some other digipeater */
				free_p(bp);
			}
			return;
		}
	}
	/* Packet has passed all repeaters, now look at destination */
	ours = 0;
	if(addreq(&ap[0],&ax25_bdcst)){
		multicast = 1;	/* Broadcast packet */
	} else if(addreq(&ap[0],&mycall)){
		multicast = 0;	/* Packet directed at us */
		ours = 1;
#if	0
	/* we really do want to see all of the packets, later on */
	} else {
		/* Not for us */
		free_p(bp);
		return;
#endif
	}
	/* Now remove the header and the control field. Note: This will
	 * have to be changed if the connected mode of AX.25 (ugh!) is ever
	 * implemented
	 */
	pullup(&bp,NULLCHAR,1 + addrsize * AXALEN);

	/* Examine the protocol ID field and switch to the right protocol */
	if(pullup(&bp,&pid,1) != 1)
		return;	/* No PID, probably not an I-frame */
	switch(pid & 0xff){
	case PID_ARP:
		arp_input(interface,bp);
		break;
	case PID_IP:
		if (!(ours || multicast)) {
			free_p(bp);
			break;
		}
		ip_route(bp,multicast);
		break;
#ifdef	NETROM
	case PID_NETROM:
		netrom_input(interface, bp);
		break;
#endif
	default:
		free_p(bp);
		break;
	}
}
/* Display or change our AX.25 address */
domycall(argc,argv)
int argc;
char *argv[];
{
	char buf[15];

	if(argc < 2){
		pax25(buf,&mycall);
		printf("%s\r\n",buf);
		return 0;
	}
	if(setcall(&mycall,argv[1]) == -1)
		return -1;
	mycall.ssid |= E;
}
/*
 * setcall - convert callsign plus substation ID of the form
 * "KA9Q-0" to AX.25 (shifted) address format
 *   Address extension bit is left clear
 *   Return -1 on error, 0 if OK
 */
int
setcall(out,call)
struct ax25_addr *out;
char *call;
{
	int csize;
	unsigned ssid;
	register int i;
	register char *cp,*dp;
	char c,*index();

	if(out == (struct ax25_addr *)NULL || call == NULLCHAR || *call == '\0'){
		return -1;
	}
	/* Find dash, if any, separating callsign from ssid
	 * Then compute length of callsign field and make sure
	 * it isn't excessive
	 */
	dp = index(call,'-');
	if(dp == NULLCHAR)
		csize = strlen(call);
	else
		csize = dp - call;
	if(csize > 6)
		return -1;
	/* Now find and convert ssid, if any */
	if(dp != NULLCHAR){
		dp++;	/* skip dash */
		ssid = atoi(dp);
		if(ssid > 15)
			return -1;
	} else
		ssid = 0;
	/* Copy upper-case callsign, left shifted one bit */
	cp = out->call;
	for(i=0;i<csize;i++){
		c = *call++;
		if(islower(c))
			c = toupper(c);
		*cp++ = c << 1;
	}
	/* Pad with shifted spaces if necessary */
	for(;i<6;i++)
		*cp++ = ' ' << 1;
	
	/* Insert substation ID field and set reserved bits */
	out->ssid = 0x60 | (ssid << 1);
	return 0;
}
static
addreq(a,b)
register struct ax25_addr *a,*b;
{
	if(bcmp(a->call,b->call,ALEN) != 0)
		return 0;
	if((a->ssid & SSID) != (b->ssid & SSID))
		return 0;
	return 1;
}
/* Convert encoded AX.25 address to printable string */
pax25(e,addr)
char *e;
struct ax25_addr *addr;
{
	register int i;
	char c,*cp;

	cp = addr->call;
	for(i=6;i != 0;i--){
		c = (*cp++ >> 1) & 0x7f;
		if(c == ' ')
			break;
		*e++ = c;
	}
	sprintf(e,"-%d",(addr->ssid >> 1) & 0xf);	/* ssid */
}
/* Print a string of AX.25 addresses in the form
 * "KA9Q-0 [via N4HY-0,N2DSY-2]"
 */
psax25(e,addr)
register char *e;
register struct ax25_addr *addr;
{
	int i;

	for(i=0;;i++){
		pax25(e,addr);
		if(addr->ssid & E)
			break;
		if(i == 0)
			strcat(e," via ");
		else
			strcat(e,",");
		e += strlen(e);
		/* addr++; */
		addr = (struct ax25_addr *) ((char *)addr + AXALEN);
	}
}
/* Return a pointer to the control byte in the given frame */
char *
cbyte(fp)
register struct mbuf *fp;
{
	register char *cp;
	register unsigned cnt;

	if(fp == NULLBUF || fp->data == NULLCHAR)
		return NULLCHAR;

	cnt = fp->cnt;
	cp = fp->data;
	while(cnt != 0 && (*cp & E) == 0){
		cnt--;
		cp++;
	}
	/* If the address field never ended, cnt = 0; if it ended
	 * on the last byte of a frame, cnt = 1. In either case,
	 * there is no control field
	 */
	if(cnt <= 1)
		return NULLCHAR;
	else
		return cp + 1;
}
dokiss(argc,argv)
int argc;
char *argv[];
{
	struct interface *ifp;
	struct mbuf *hbp;
	int i;
	char *cp;

	if(argc < 2){
		printf("Interface name missing\r\n");
		return 1;
	}
	for(ifp=ifaces;ifp != NULLIF;ifp = ifp->next){
		if(strcmp(argv[1],ifp->name) == 0)
			break;
	}
	if(ifp == NULL){
		printf("Interface \"%s\" unknown\r\n",argv[1]);
		return 1;
	}
	if(ifp->output != kiss_output){
		printf("Interface \"%s\" not kiss\r\n",argv[1]);
		return 1;
	}
	if(argc < 3){
		printf("Data field missing\r\n");
		return 1;
	}
	/* Number of bytes in message == number of args - 2, since
	 * first two args are "kiss" and the interface name
	 */
	if((hbp = alloc_mbuf(argc - 2)) == NULLBUF){
		free_p(hbp);
		return;
	}
	hbp->cnt = argc - 2;
	hbp->next = NULL;
	for(i=2,cp = hbp->data;i < argc;i++,cp++){
		*cp = htoi(argv[i]);
	}
	slipq(ifp->dev,hbp);
}
#ifdef	TRACE

/* Dump an AX.25 packet header */
ax25_dump(abp)
struct mbuf *abp;
{
	struct mbuf *bp;
	struct ax25_addr src,dest,addr;
	char tmp[20],*cp,control,cmdrsp,pid;
	int16 len,type,ftype();

	dup_p(&bp,abp,0,len_mbuf(abp));

	/* Read and print the destination and source addresses */
	if(pullup(&bp,(char *)&dest,AXALEN) != AXALEN)
		goto quit;
	if(pullup(&bp,(char *)&src,AXALEN) != AXALEN)
		goto quit;

	pax25(tmp,&src);
	printf("AX25: %s",tmp);
	pax25(tmp,&dest);
	printf("->%s",tmp);

	if((src.ssid & E) == 0){
		/* Find the last address entry in the digi string */
		printf(" v");
		while(pullup(&bp,(char *)&addr,AXALEN) == AXALEN){
			pax25(tmp,&addr);
#ifdef	AMIGA
			/* this is just personal preference; reminds me of 
			   the WA8DED TNC-1 code */
			printf(" %s%s",tmp,(addr.ssid & REPEATED) ? "*":"");
#else
			printf(" %s%s",tmp,(addr.ssid & REPEATED) ? "H":"");
#endif
			if(addr.ssid & E)
				break;	/* Found it */
		}
	}
	if(pullup(&bp,&control,1) != 1)
		goto quit;

	putchar(' ');
	type = ftype(control);
	switch(type){
	case I:
		printf("I");
		break;
	case SABM:
		printf("SABM");
		break;
	case DISC:
		printf("DISC");
		break;
	case DM:
		printf("DM");
		break;
	case UA:
		printf("UA");
		break;
	case RR:
		printf("RR");
		break;
	case RNR:
		printf("RNR");
		break;
	case REJ:
		printf("REJ");
		break;
	case FRMR:
		printf("FRMR");
		break;
	case UI:
		printf("UI");
		break;
	default:
		printf("[invalid]");
	}

	if((dest.ssid & C) != (src.ssid & C)){
		if(dest.ssid & C)
			cmdrsp = COMMAND;
		else
			cmdrsp = RESPONSE;
	} else 
		cmdrsp = UNKNOWN;
	/* Dump poll/final bit */
	if(control & PF){
		switch(cmdrsp){
		case COMMAND:
			printf("(P)");
			break;
		case RESPONSE:
			printf("(F)");
			break;
		default:
			printf("(P/F)");
			break;
		}
	}
	/* Dump sequence numbers */
	if((type & 0x3) != U)	/* I or S frame? */
		printf(" NR=%d",(control>>5)&7);
	if((type & 0x1) == I)	/* I frame? */
		printf(" NS=%d",(control>>1)&7);

	if(pullup(&bp,&pid,1) != 1)
		goto quit;
	printf(" pid 0x%x\r\n",pid & 0xff);
	if((trace & TRACE_HDR) > 2){
		switch(pid & 0xff){
		case PID_ARP:
			arp_dump(bp);
			break;
		case PID_IP:
			ip_dump(bp);
			break;
#ifdef	NETROM
		case PID_NETROM:
			netrom_dump(bp);
			break;
#endif
		}
	}
	free_p(bp);
	fflush(stdout);
	return;
quit:	/* I hate go-to's, but sometimes there's no real choice */
	free_p(bp);
	printf("\r\n");
	fflush(stdout);
}
/* Figure out the frame type from the control field
 * This is done by masking out any sequence numbers and the
 * poll/final bit after determining the general class (I/S/U) of the frame
 */
static
int16
ftype(control)
register char control;
{
	if((control & 1) == 0)	/* An I-frame is an I-frame... */
		return I;
	if(control & 2)			/* U-frames use all except P/F bit for type */
		return(control & ~PF);
	else					/* S-frames use low order 4 bits for type */
		return(control & 0xf);
}
#endif
