/* User subroutines for AX.25 */
#include "global.h"
#include "mbuf.h"
#include "timer.h"
#include "iface.h"
#include "ax25.h"
#include "lapb.h"
#include <ctype.h>

/* Open an AX.25 connection */
struct ax25_cb *
open_ax25(addr,window,r_upcall,t_upcall,s_upcall,iface,user)
struct ax25 *addr;		/* Addresses */
int16 window;			/* Window size in bytes */
void (*r_upcall)();		/* Receiver upcall handler */
void (*t_upcall)();		/* Transmitter upcall handler */
void (*s_upcall)();		/* State-change upcall handler */
struct interface *iface;	/* Hardware interface structure */
char *user;			/* User linkage area */
{
	register struct ax25_cb *axp;
	struct ax25_call *axc;
	struct ax25_cb *cr_ax25();
	void lapbstate();

	if((axc = find_axcall(ax25_excl,&addr->dest)) != NULLAXCALL &&
	    ((axc->flags & MULTI_IF) || axc->interface == iface))
		return NULLAX25;
	if((axp = cr_ax25(&addr->dest,&addr->source)) == NULLAX25)
		return NULLAX25;
	ASSIGN(axp->addr,*addr);
	if(addr->ndigis != 0){
		axp->t1.start *= (addr->ndigis + 1);
		axp->t2.start *= (addr->ndigis + 1);
		/*axp->t3.start *= (addr->ndigis + 1);
		axp->t4.start *= (addr->ndigis + 1);*/
	}
	axp->window = window;
	axp->r_upcall = r_upcall;
	axp->t_upcall = t_upcall;
	axp->s_upcall = s_upcall;
	axp->interface = iface;
	axp->user = user;

	if (axp->state == DISCONNECTED){
		/* Don't send anything if the connection already exists */
		lapbstate(axp,SETUP,LAPBNOMS);
		sendctl(axp,COMMAND,SABM|PF);
	}
	return axp;
}

/* Send data on an AX.25 connection. Caller must provide PID */
int
send_ax25(axp,bp)
struct ax25_cb *axp;
struct mbuf *bp;
{
	if(axp == NULLAX25 || bp == NULLBUF || axp->fin){
		free_p(bp);
		return -1;
	}
	enqueue(&axp->txq,bp);
	return lapb_output(axp);
}

/* Send data on an AX.25 connection, creating packets sized "paclen" */
sendp_ax25(axp,bp,pid)
register struct ax25_cb *axp;
struct mbuf *bp;
char pid;
{
	register struct mbuf *tbp;
	int16 bsize;

	if(axp == NULLAX25 || bp == NULLBUF)
		return;
	bsize = len_mbuf(bp);
	while(bsize != 0){
		if (bsize <= axp->paclen) {
			if((tbp = pushdown(bp,1)) == NULLBUF) {/* make space for PID */
				free_p(bp);
				break;		/* out of memory! */
			}
			bsize = 0;
		} else {
			/* Allocate buffer, allowing space for PID */
			if((tbp = alloc_mbuf(axp->paclen + 1)) == NULLBUF) {
				free_p(bp);
				break;		/* out of memory! */
			}
			tbp->cnt = 1 + pullup(&bp,tbp->data + 1,axp->paclen);
			bsize -= axp->paclen;
		}
		*tbp->data = pid;
		send_ax25(axp,tbp);
	}
}

/* Send UI frames to an AX.25-type iface, creating packets sized "paclen" */
sendui_ax25(ifp,addr,bp,pid)
struct interface *ifp;
struct ax25 *addr;
struct mbuf *bp;
char pid;
{
	register struct mbuf *tbp;
	int16 bsize;
	extern int16 paclen;

	if(ifp == NULLIF || bp == NULLBUF)
		return;
	bsize = len_mbuf(bp);
	while(bsize != 0){
		if (bsize <= paclen) {
			if((tbp = pushdown(bp,1)) == NULLBUF) {/* make space for PID */
				free_p(bp);
				break;		/* out of memory! */
			}
			bsize = 0;
		} else {
			/* Allocate buffer, allowing space for PID */
			if((tbp = alloc_mbuf(paclen + 1)) == NULLBUF) {
				free_p(bp);
				break;		/* out of memory! */
			}
			tbp->cnt = 1 + pullup(&bp,tbp->data + 1,paclen);
			bsize -= paclen;
		}
		*tbp->data = pid;
		senduiframe(ifp,addr,tbp);
	}
}

/* Receive incoming data on an AX.25 connection */
struct mbuf *
recv_ax25(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	struct mbuf *bp;
	unsigned rxqlen;

	if(axp->rxq == NULLBUF)
		return NULLBUF;

	if ((rxqlen = len_mbuf(axp->rxq)) <= cnt || cnt == 0){
		bp = axp->rxq;
		axp->rxq = NULLBUF;
		cnt = rxqlen;
	} else {
		if ((bp = alloc_mbuf(cnt)) == NULLBUF)
			return NULLBUF;

		cnt = bp->cnt = pullup(&axp->rxq,bp->data,cnt);
	}
	/* If this has un-busied us, send a RR to reopen the window */
	if(rxqlen >= axp->window && (rxqlen - cnt) < axp->window)
		sendctl(axp,RESPONSE,RR);
	return bp;
}

/* Get a line of text from an AX.25 connection (up to \r) */
/* The textline may span multiple incoming packets, so NULLBUF is returned */
/* as long as the line is incomplete (and it is saved) */
struct mbuf *
recvl_ax25(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	struct mbuf *bp;
	unsigned rxqlen;

	if(axp->rxq == NULLBUF)
		return NULLBUF;

	rxqlen = len_mbuf(axp->rxq);

	if ((bp = pullline(&axp->rxq,cnt)) != NULLBUF){
		/* If this has un-busied us, send a RR to reopen the window */
		if(rxqlen >= axp->window && (rxqlen - bp->cnt) < axp->window)
			sendctl(axp,RESPONSE,RR);
	}
	return bp;
}

/* "Gracefully Close" an AX.25 connection */
close_ax25(axp)
struct ax25_cb *axp;
{
	axp->fin = YES;			/* set FIN flag */
	if (axp->txq == NULLBUF &&	/* no outstanding frames? */
	    axp->state != DISCPENDING)	/* not already disconnecting? */
		disc_ax25(axp);		/* start disconnecting now */
}

/* Close an AX.25 connection */
disc_ax25(axp)
struct ax25_cb *axp;
{
	void lapbstate();

	switch (axp->state)
	{
	case DISCONNECTED:
		break;			/* Ignored */
	case DISCPENDING:		/* was pending, just drop it */
		lapbstate(axp,DISCONNECTED,LAPBDISC);
		del_ax25(axp);
		break;
	case CONNECTED:			/* was connected, full retries */
		lapbstate(axp,DISCPENDING,LAPBNOMS);
		sendctl(axp,COMMAND,DISC|PF);
		break;
	default:			/* SETUP */
		lapbstate(axp,DISCPENDING,LAPBNOMS);
		axp->retries = axp->n2 - 2; /* try 3 times only */
		sendctl(axp,COMMAND,DISC|PF);
		break;
	}
}

/* Verify that axp points to a valid ax25 control block */
ax25val(axp)
struct ax25_cb *axp;
{
	register struct ax25_cb *axp1;
	register int i;

	if(axp == NULLAX25)
		return 0;	/* Null pointer can't be valid */
	for(i=0; i < NHASH; i++)
		for(axp1 = ax25_cb[i];axp1 != NULLAX25; axp1 = axp1->next)
			if(axp1 == axp)
				return 1;
	return 0;
}

/* Force a retransmission */
kick_ax25(axp)
struct ax25_cb *axp;
{
	void recover();

	if(!ax25val(axp))
		return -1;
	recover((int *)axp);
	return 0;
}

/* Abruptly terminate an AX.25 connection */
reset_ax25(axp)
struct ax25_cb *axp;
{
	void lapbstate();

	lapbstate(axp,DISCONNECTED,LAPBNOMS);
	del_ax25(axp);
}
