/* User interface subroutines for AX.25
 * Copyright 1991 Phil Karn, KA9Q
 */
#include "global.h"
#ifdef AX25
#include "mbuf.h"
#include "timer.h"
#include "iface.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ax25user.c,v 1.13 2000/05/09 16:48:24 brian Exp $";
#endif

void reset_all_ax25 (void);


/* Open an AX.25 connection */
struct ax25_cb *
open_ax25 (iface, local, theremote, mode, window, r_upcall, t_upcall, s_upcall, user)
struct iface *iface;		/* Interface */
char *local;			/* Local address */
char *theremote;		/* theremote address */
int mode;			/* active/passive/server */
int16 window;			/* Window size in bytes */
void (*r_upcall) (struct ax25_cb *, int);	/* Receiver upcall handler */
void (*t_upcall) (struct ax25_cb *, int);	/* Transmitter upcall handler */
void (*s_upcall) (struct ax25_cb *, int, int);	/* State-change upcall handler */
int user;			/* User linkage area */
{
struct ax25_cb *axp;
char remtmp[AXALEN];

	if (theremote == NULLCHAR) {
		theremote = remtmp;
		(void) setcall (theremote, " ");
	}
	if ((axp = find_ax25 (local, theremote, iface)) != NULLAX25 && axp->state != LAPB_DISCONNECTED)
		return NULLAX25;/* Only one to a customer */
	if (axp == NULLAX25 && (axp = cr_ax25 (local, theremote, iface)) == NULLAX25)
		return NULLAX25;

	axp->iface = iface;
	axp->window = window;
	axp->r_upcall = r_upcall;
	axp->t_upcall = t_upcall;
	axp->s_upcall = s_upcall;
	axp->user = user;

	switch (mode) {
		case AX_SERVER:
			axp->flags.clone = 1;
			/* fall through */
		case AX_PASSIVE:	
			axp->state = LAPB_LISTEN;
			return axp;
		case AX_ACTIVE:
		default:
			break;
	}
	switch (axp->state) {
		case LAPB_DISCONNECTED:
			est_link (axp);
			lapbstate (axp, LAPB_SETUP);
			break;
		case LAPB_SETUP:
			free_q (&axp->txq);
			break;
		case LAPB_RECOVERY:
		case LAPB_CONNECTED:
			free_q (&axp->txq);
			est_link (axp);
			lapbstate (axp, LAPB_SETUP);
			break;
		case LAPB_DISCPENDING:	/* Ignore */
		default:
			break;
	}
	return axp;
}


/* Send data on an AX.25 connection. Caller provides optional PID. If
 * a PID is provided, then operate in stream mode, i.e., a large packet
 * is automatically packetized into a series of paclen-sized data fields.
 *
 * If pid == -1, it is assumed the packet (which may actually be a queue
 * of distinct packets) already has a PID on the front and it is passed
 * through directly even if it is very large.
 */
int
send_ax25 (axp, bp, pid)
struct ax25_cb *axp;
struct mbuf *bp;
int pid;
{
struct mbuf *bp1;
int16 offset, len, size;

	if (axp == NULLAX25 || bp == NULLBUF) {
		free_p (bp);
		return -1;
	}
	if (pid != -1) {
		offset = 0;
		len = len_p (bp);
		/* It is important that all the pushdowns be done before
		 * any part of the original packet is freed.
		 * Otherwise the pushdown might erroneously overwrite
		 * a part of the packet that had been duped and freed.
		 */
		while (len != 0) {
			size = min (len, axp->paclen);
			(void) dup_p (&bp1, bp, offset, size);
			len -= size;
			offset += size;
			bp1 = pushdown (bp1, 1);
			bp1->data[0] = uchar(pid);
			enqueue (&axp->txq, bp1);
		}
		free_p (bp);
	} else {
		enqueue (&axp->txq, bp);
	}
	return lapb_output (axp);
}


/*lint -esym(18,recv_ax25) */
/* Receive incoming data on an AX.25 connection */
struct mbuf *
recv_ax25 (struct ax25_cb *axp, int16 cnt)
{
struct mbuf *bp;

	if (axp->rxq == NULLBUF)
		return NULLBUF;

	if (cnt == 0) {
		/* This means we want it all */
		bp = axp->rxq;
		axp->rxq = NULLBUF;
	} else {
		bp = ambufw (cnt);
		bp->cnt = pullup (&axp->rxq, bp->data, cnt);
	}
	/* If this has un-busied us, send a RR to reopen the window */
	if (len_p (axp->rxq) < axp->window
	    && (len_p (axp->rxq) + bp->cnt) >= axp->window)
		(void) sendctl (axp, LAPB_RESPONSE, RR);

	return bp;
}


/* Close an AX.25 connection */
int
disc_ax25 (axp)
struct ax25_cb *axp;
{
	if (axp == NULLAX25)
		return -1;
	switch (axp->state) {
		case LAPB_LISTEN:
			del_ax25 (axp);
			break;
		case LAPB_DISCPENDING:
			lapbstate (axp, LAPB_DISCONNECTED);
			break;
		case LAPB_CONNECTED:
		case LAPB_RECOVERY:
			free_q (&axp->txq);
			axp->retries = 0;
			(void) sendctl (axp, LAPB_COMMAND, DISC | PF);
			stop_timer (&axp->t3);
		    start_detached_timer (&axp->t1);
			lapbstate (axp, LAPB_DISCPENDING);
			break;
		case LAPB_DISCONNECTED:
		default:
			break;	/* Ignored */
	}
	return 0;
}


/* Verify that axp points to a valid ax25 control block */
int
ax25val (axp)
struct ax25_cb *axp;
{
register struct ax25_cb *axp1;

	if (axp == NULLAX25)
		return 0;	/* Null pointer can't be valid */
	for (axp1 = Ax25_cb; axp1 != NULLAX25; axp1 = axp1->next)
		if (axp1 == axp)
			return 1;
	return 0;
}


/* Force a retransmission */
int
kick_ax25 (axp)
struct ax25_cb *axp;
{
	if (!ax25val (axp))
		return -1;
	recover (axp);
	return 0;
}


/* Abruptly terminate an AX.25 connection */
int
reset_ax25 (axp)
struct ax25_cb *axp;
{
	void (*upcall) (struct ax25_cb *, int);

	if (axp == NULLAX25)
		return -1;
	/* Be nice and send a DM - WG7J */
	(void) disc_ax25 (axp);
	upcall = (void (*)(struct ax25_cb *, int)) axp->s_upcall;
	lapbstate (axp, LAPB_DISCONNECTED);
	/* Clean up if the standard upcall isn't in use */
	if (upcall != (void (*)(struct ax25_cb *, int)) s_ascall)
		del_ax25 (axp);
	return 0;
}



/* Display AX.25 link level control blocks */
void
reset_all_ax25 (void)
{
	while (Ax25_cb != NULLAX25)
		(void) reset_ax25 (Ax25_cb);
}

#endif
