/* Low level AX.25 frame processing - address header */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "arp.h"
#include "slip.h"
#include "ax25.h"
#include "lapb.h"
#include <ctype.h>

char axbdcst[AXALEN];	/* Broadcast address (QST-0), network format */
int digipeat = 1;	/* Controls single-band standard digipeating */
struct mbuf *axloopq = NULLBUF; /* Loopback queue for AX.25 packets */

/* Send IP datagrams across an AX.25 link */
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();
	struct ax25_cb *axp,*find_ax25(),*open_ax25();
	struct ax25 addr;
	struct ax25_addr destaddr;
	struct mbuf *tbp;
	extern int16 axwindow;
	void ax_incom();
	int16 size,bsize,seq;

	if((hw_addr = res_arp(interface,ARP_AX25,gateway,bp)) == NULLCHAR)
		return 0;	/* Wait for address resolution */

	if(delay || (!reliability && !(interface->flags & IF_CONNECT_MODE))){
		/* Use UI frame */
		return (*interface->output)(interface,hw_addr,
			interface->hwaddr,PID_FIRST|PID_LAST|PID_IP,bp);
	}
	/* Reliability is needed; use I-frames in AX.25 connection */
	memcpy(destaddr.call,hw_addr,ALEN);
	destaddr.ssid = hw_addr[ALEN];

	if((axp = find_ax25(&destaddr,(struct ax25_addr *)interface->hwaddr)) == NULLAX25 ||
	    (axp->state != CONNECTED && axp->state != SETUP)){
		/* Open a new connection or reinitialize the old one */
		atohax25(&addr,hw_addr,(struct ax25_addr *)interface->hwaddr);
		axp = open_ax25(&addr,axwindow,ax_incom,NULLVFP,NULLVFP,interface,(char *)0);
		if(axp == NULLAX25){
			free_p(bp);
			return -1;
		}
	}
	/* If datagram is too big for one frame, send all but the last with
	 * the extension PID. Note: the copy to a new buf is necessary because
	 * AX.25 may continue retransmitting the frame after a local TCB has
	 * gone away, and using the buf directly would cause heap garbage to be
	 * transmitted. Besides, nobody would ever use AX.25 anywhere
	 * high performance is needed anyway...
	 */
	bsize = len_mbuf(bp);
	seq = 0;
	while(bsize != 0){
		size = min(bsize,axp->paclen);
		/* Allocate buffer, allowing space for PID */
		if((tbp = alloc_mbuf(size + 1)) == NULLBUF)
			break;		/* out of memory! */
		*tbp->data = PID_IP;
		if(seq++ == 0)
			*tbp->data |= PID_FIRST;	/* First in sequence */
		if(size == bsize)
			*tbp->data |= PID_LAST;		/* That's all of it */
		/* else more to follow */

		tbp->cnt = 1;
		tbp->cnt += pullup(&bp,tbp->data + 1,size);
		send_ax25(axp,tbp);
		bsize -= size;
	}
	free_p(bp);	/* Shouldn't be necessary */
	return 0;
}
/* Add AX.25 link header and send packet.
 * Note that the calling order here must match ec_output
 * since ARP also uses it.
 */
ax_output(interface,dest,source,pid,data)
struct interface *interface;
char *dest;		/* Destination AX.25 address (7 bytes, shifted) */
			/* Also includes digipeater string */
char *source;		/* Source AX.25 address (7 bytes, shifted) */
char pid;		/* Protocol ID */
struct mbuf *data;	/* Data field (follows PID) */
{
	struct mbuf *abp,*cbp,*htonax25();
	struct ax25 addr;

	/* Allocate mbuf for control and PID fields, and fill in */
	if((cbp = pushdown(data,2)) == NULLBUF){
		free_p(data);
		return -1;
	}
	cbp->data[0] = UI;
	cbp->data[1] = pid;

	atohax25(&addr,dest,(struct ax25_addr *)source);
	if((abp = htonax25(&addr,cbp)) == NULLBUF){
		free_p(cbp);	/* Also frees data */
		return -1;
	}
	/* This shouldn't be necessary because redirection has already been
	 * done at the IP router layer, but just to be safe...
	 */
	if(interface->forw != NULLIF)
		return (*interface->forw->raw)(interface->forw,abp);
	else
		return (*interface->raw)(interface,abp);
}
/* 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 or NODES-0, kick it upstairs depending on the protocol ID.
 */
int
ax_recv(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	void arp_input();
	int ip_route();
	struct ax25_addr addr,*ap;
	struct mbuf *htonax25(),*hbp;
	struct ax25 hdr;
	register struct ax25_cb *axp;
	struct ax25_cb *find_ax25(),*cr_ax25();
	register struct ax25_call *axc;
	struct ax25_call *axd;
	int timerscale,setdigi;
	extern int16 t1init,t2init,t3init,t4init;

	/* Pull header off packet and convert to host structure */
	if(ntohax25(&hdr,&bp) < 0){
		/* Something wrong with the header */
		free_p(bp);
		return;
	}
	/* Update the users liatifor persistence calculation */
	upd_nusers(interface,hdr.source.call);
	/* Update the MHEARD liatifor this interface */
	if(interface->mheard != 0){	/* first check if we have to keep it */
		updmheard(interface,1,&hdr); /* update the liat */
	}
	/* check source address. it must NOT appear in the exclude liat */
	ASSIGN(addr,hdr.source);	/* get the source address */
	addr.ssid = 0;			/* discard the SSID */
	if((axc = find_axcall(ax25_excl,&addr)) != NULLAXCALL &&
	    ((axc->flags & MULTI_IF) || axc->interface == interface)) {
		free_p(bp);
		return;
	}
	timerscale = hdr.ndigis + 1;	/* default scaling of timer values */

	/* Scan, looking for our call in the repeater fields, if any.
	 * Repeat appropriate packets.
	 */
	for(ap = &hdr.digis[0]; ap < &hdr.digis[hdr.ndigis]; ap++){
	    if(ap->ssid & REPEATED)
		continue;	/* Already repeated */
	    /* Check if packet is directed to us as a digipeater */
	    if((axc = find_axcall(ax25_digi,ap)) != NULLAXCALL){
		/* check dest address. it must NOT appear in the exclude liat */
		ASSIGN(addr,hdr.dest);	/* get the dest address */
		addr.ssid = 0;		/* discard the SSID */
		if((axd = find_axcall(ax25_excl,&addr)) != NULLAXCALL &&
		    ((axd->flags & MULTI_IF) ||
		     axd->interface == interface || axd->interface == axc->interface)) {
			free_p(bp);
			return;		/* if any doubt, refuse it */
		}
		switch (axc->mode)	/* check the digi mode */
		{
		case DIGIPEAT:		/* bare digipeater */
		    if (axc->interface == interface){ /* this band? */
			if (!digipeat)	/* in-band digi disabled? */
			    break;	/* throw it away */
		    } else {		/* not "home", gateway request */
			if (axc->flags & DIGIGATEWAY &&
			    (axd = find_axcall(ax25_digi,interface->hwaddr))
				!= NULLAXCALL &&
			     axd->mode == DIGIPEAT &&
			     axd->flags & DIGIGATEWAY){
				 /* swap callsign and interface */
				 ASSIGN(*ap,*((struct ax25_addr *) interface->hwaddr));
				 interface = axc->interface;
			} else {
			    break;	/* gateway conditions not fulfilled... */
			}
		    }

		    /* kick it back out */
		    ap->ssid |= REPEATED;
		    if((hbp = htonax25(&hdr,bp)) != NULLBUF){
			if(interface->forw != NULLIF)
			    (*interface->forw->raw)(interface->forw,hbp);
			else
			    (*interface->raw)(interface,hbp);
			tnc2_kissrcv(interface,&hdr,bp,4);
			bp = NULLBUF;
		    }
		    break;

		case DIGICONNECT:	/* digipeater simulating connection */
		    if ((axc->flags & MULTI_IF) ||
			axc->interface == interface ||
			(axc->flags & DIGIGATEWAY)){
			/* address is okay */
			if (bp != NULLBUF){
			    timerscale = (int) (ap - &hdr.digis[0]) + 1;
			    goto for_us;/* handle as if addressed to us */
			}
		    }
		    break;
		}
	    }
	    tnc2_kissrcv(interface,&hdr,bp,4);
	    free_p(bp);			/* Dispose if not forwarded */
	    return;
	}
	/* Packet has passed all repeaters, now look at destination */
	if((axc = find_axcall(ax25_call,&hdr.dest)) == NULLAXCALL ||
	    !((axc->flags & MULTI_IF) || axc->interface == interface)) {
		/* Not for us, or wrong interface for this callsign */
		tnc2_kissrcv(interface,&hdr,bp,3);
		free_p(bp);
		return;
	}

	if(bp == NULLBUF){
		/* Nothing left */
		return;
	}
	/* Sneak a peek at the control field. This kludge is necessary because
	 * AX.25 lacks a proper protocol ID field between the address and LAPB
	 * sublayers; a control value of UI indicates that LAPB is to be
	 * bypassed.
	 */
	if((*bp->data & ~PF) == UI && axc->mode == IP_ARP_CON){
		char pid;

		tnc2_kissrcv(interface,&hdr,bp,2);
		(void) pullchar(&bp);
		if(pullup(&bp,&pid,1) != 1)
			return;		/* No PID */
		/* Handle packets. Multi-frame messages are not allowed */
		switch(pid & (PID_FIRST | PID_LAST | PID_PID))
		{
		case (PID_IP | PID_FIRST | PID_LAST):
			if(!(axc->flags & MULTICAST) || axc->flags & IP_ARP_MULTI){
				ip_route(bp,axc->flags & MULTICAST);
				return;
			}
		case (PID_ARP | PID_FIRST | PID_LAST):
			if(!(axc->flags & MULTICAST) || axc->flags & IP_ARP_MULTI){
				arp_input(interface,bp);
				return;
			}
		case (PID_NETROM | PID_FIRST | PID_LAST):
			/* NET/ROM is very poorly layered. The meaning of the stuff
			 * following the PID of CF depends on what's in the AX.25 dest
			 * field.
			 */
			if(axc->flags & NETROM_MULTI && hdr.ndigis == 0){
				nr_nodercv(interface,&hdr.source,bp);
				return;
			}
			break;
		}
		free_p(bp);
		return;
	}

	if(axc->flags & MULTICAST){	/* don't allow connects to broadcast */
		tnc2_kissrcv(interface,&hdr,bp,2);
		free_p(bp);
		return;
	}

for_us: if (axc->port == TNC2PORT &&
	    (axc->mode == CONNECT || axc->mode == DIGICONNECT) &&
	    tnc2_kissrcv(interface,&hdr,bp,1)) {
		free_p(bp);
		return;
	}

	tnc2_kissrcv(interface,&hdr,bp,2);

	setdigi = 0;			/* no need to set digi path (yet) */
	/* Find the source/dest address pair in hash table */
	if((axp = find_ax25(&hdr.source,&hdr.dest)) == NULLAX25){
		/* He is new! First check for reasonable callsign */
		if(valid_addr(&hdr.source) != 0) {
			free_p(bp);
			return;
		}
		/* Create a new ax25 entry for this guy,
		 * insert into hash table keyed on his address,
		 * and initialize table entries
		 */
		if((axp = cr_ax25(&hdr.source,&hdr.dest)) == NULLAX25){
			free_p(bp);
			return;
		}
		/* Swap source and destination callsigns */
		ASSIGN(axp->addr.dest,hdr.source);
		ASSIGN(axp->addr.source,hdr.dest);
		setdigi++;		/* also set the digi string and interface */

		/* see if it is a connection to an ax25 port */
		if (axc->port != 0 &&	/* a port ? */
		    (axc->mode == CONNECT || axc->mode == DIGICONNECT)){
			axp->r_upcall = axc->r_upcall;	/* set upcall handlers */
			axp->t_upcall = axc->t_upcall;
			axp->s_upcall = axc->s_upcall;
			axp->user = (char *) axc;	/* link to ax call */
		}
	}

	/* set digi path for new connections, but always set it when the
	 * received frame is an SABM.  This allows other stations to
	 * reconnect to us with a different digi path (TNC2 reconnect cmd).
	 * same holds for the interface the connection is on.
	 */
	if(setdigi || ((*bp->data & ~PF) == SABM)){
		if(hdr.ndigis > 0){
			int i,j;

			/* Construct reverse digipeater path */
			for(i=hdr.ndigis-1,j=0;i >= 0;i--,j++){
				ASSIGN(axp->addr.digis[j],hdr.digis[i]);
				axp->addr.digis[j].ssid ^= REPEATED;
				axp->addr.digis[j].ssid &= ~E;
			}
		}
		/* Scale timers to account for extra delay */
		axp->t1.start = t1init * timerscale;
		axp->t2.start = t2init * timerscale;
		/*axp->t3.start = t3init * timerscale;
		axp->t4.start = t4init * timerscale;*/
		axp->addr.ndigis = hdr.ndigis;

		axp->interface = interface;
	}

	if(hdr.cmdrsp == UNKNOWN)
		axp->proto = V1;	/* Old protocol in use */
	else
		axp->proto = V2;

	lapb_input(axp,hdr.cmdrsp,bp);
}

/* General purpose AX.25 frame output */
int
sendframe(axp,cmdrsp,ctl,data)
struct ax25_cb *axp;
char cmdrsp;
char ctl;
struct mbuf *data;
{
	struct mbuf *hbp,*cbp,*ibp,*htonax25();
	struct ax25_call *axc;
	int rv,i;

	if(axp == NULLAX25 || axp->interface == NULLIF)
		return -1;

	/* Add control field */
	if((cbp = pushdown(data,1)) == NULLBUF){
		free_p(data);
		return -1;
	}
	cbp->data[0] = ctl;

	axp->addr.cmdrsp = cmdrsp;
	/* Create address header */
	if((hbp = htonax25(&axp->addr,cbp)) == NULLBUF){
		free_p(cbp);
		return -1;
	}

	/* First make a check to see if it's directed to ourselves */
	for(i = 0; i < axp->addr.ndigis; i++)
	    if(!(axp->addr.digis[i].ssid & REPEATED))
		break;
	if((i == axp->addr.ndigis &&
	    (axc = find_axcall(ax25_call,&axp->addr.dest)) != NULLAXCALL) ||
	   (i < axp->addr.ndigis &&
	    (axc = find_axcall(ax25_digi,&axp->addr.digis[i])) != NULLAXCALL) &&
	   !(axc->flags & MULTICAST) &&
	   ((axc->flags & (MULTI_IF|DIGIGATEWAY)) || axc->interface == axp->interface)) {
		/* put it on the ax loopback queue with interface ptr */
		if((ibp = alloc_mbuf(4 + len_mbuf(hbp))) == NULLBUF){
			free_p(hbp);
			return -1;
		}
		put32(ibp->data,(long) axp->interface);
		ibp->cnt = 4 + pullup(&hbp,ibp->data + 4,len_mbuf(hbp));
		enqueue(&axloopq,ibp);
		rv = 0;
	} else {
		/* Not for us, really ship it */
		if(axp->interface->forw != NULLIF)
			rv = (*axp->interface->forw->raw)(axp->interface->forw,hbp);
		else
			rv = (*axp->interface->raw)(axp->interface,hbp);
	}

	if(cmdrsp == COMMAND)
		start_timer(&axp->t1);

	return rv;
}
/* AX.25 UI frame output for beacons etc */
int
senduiframe(ifp,addr,data)
struct interface *ifp;
struct ax25 *addr;
struct mbuf *data;
{
	struct mbuf *hbp,*cbp,*htonax25();

	if(ifp == NULLIF)
		return -1;

	/* Add control field (UI) */
	if((cbp = pushdown(data,1)) == NULLBUF){
		free_p(data);
		return -1;
	}
	cbp->data[0] = UI;

	addr->cmdrsp = UNKNOWN;
	/* Create address header */
	if((hbp = htonax25(addr,cbp)) == NULLBUF){
		free_p(cbp);
		return -1;
	}

	/* Ship it */
	if(ifp->forw != NULLIF)
		return (*ifp->forw->raw)(ifp->forw,hbp);
	else
		return (*ifp->raw)(ifp,hbp);
}
/* Initialize AX.25 entry in arp device table. also puts it in call table */
axarp()
{
	int psax25(),setpath();
	struct ax25_call *axc;
	struct ax25_addr ax25_bdcst;

	setcall(&ax25_bdcst,"QST-0");
	if ((axc = cr_axcall(ax25_call,&ax25_bdcst)) != NULLAXCALL){
		axc->mode  = IP_ARP_CON;
		axc->flags = MULTI_IF | MULTICAST | IP_ARP_MULTI;
	}

	memcpy(axbdcst,ax25_bdcst.call,ALEN);
	axbdcst[ALEN] = ax25_bdcst.ssid;

	arp_init(ARP_AX25,AXALEN,PID_FIRST|PID_LAST|PID_IP,
	 PID_FIRST|PID_LAST|PID_ARP,axbdcst,psax25,setpath);
}
