/* Link Access Procedures Balanced (LAPB) - with changes for rational
 * behavior over packet radio
 */
#include "global.h"
#include "mbuf.h"
#include "timer.h"
#include "ax25.h"
#include "lapb.h"

int conok = 1;			/* DG2KK */
extern struct tcb;
#define NULLTCB (struct tcb *)0	/* DG2KK */

/* Process incoming frames */
lapb_input(axp,cmdrsp,bp)
struct ax25_cb *axp;		/* Link control structure */
char cmdrsp;			/* Command/response flag */
struct mbuf *bp;		/* Rest of frame, starting with ctl */
{
	int16 ftype();
	char control;
	char frej = 0;		/* FRMR reject reason code */
	char class;		/* General class (I/S/U) of frame */
	int16 type;		/* Specific type (I/RR/RNR/etc) of frame */
	char pf;		/* extracted poll/final bit */
	char polled = 0;
	char remote[10];	/* DG2KK */

	if(bp == NULLBUF || axp == NULLAX25){
		free_p(bp);
		return;
	}
	control = pullchar(&bp);
	type = ftype(control);
	class = type & 0x3;
	pf = control & PF;

	/* Check for polls */
	if(cmdrsp == COMMAND && pf)
		polled = YES;

	/* While we're disconnected, ignore all except SABM and DISC */
	if(axp->state == DISCONNECTED && type != SABM && type != DISC){
		if(polled)
			axp->response = DM;
		goto done;
	}
	/* Process implicit acknowledgements in all but U-frames */
	if(class != U && ackours(axp,(control >> 5) & MMASK) == -1)
		frej = Z;	/* Out of range sequence number */

	if(type != I && type != FRMR && len_mbuf(bp) != 0)
		frej |= X|W;	/* I-field not allowed */

	/* Process final bit if a poll was outstanding */
	if(axp->waitack && class != U && pf && cmdrsp == RESPONSE){
		axp->waitack = NO;
		axp->retries = 0;
		stop_timer(&axp->t1);
		/* Pick up retransmission at proper point */
		axp->vs -= axp->unack;
		axp->vs &= MMASK;
		axp->unack = 0;
	}
	/* Resend FRMR for all except certain U while in error state */
	if(axp->state == FRAMEREJECT && class != U){
		goto done;
	}
	switch(type){
	case SABM:	/* Initialize or reset link */
		/* we arrive here when we receive a SABM directed to us. */
		/* put remote callsign in 'remote' */
		pax25(remote, &axp->addr.dest);		/* DG2KK */
		log(NULLTCB,"Connect request from %s (%d)", remote, conok);
if (conok != 0 || axp->state != DISCONNECTED) {		/* DG2KK */
		switch(axp->state){
		case DISCONNECTED:
		case FRAMEREJECT:
		case CONNECTED:		/* note fall-through */
			lapbstate(axp,CONNECTED);/* Resets state counters */
			axp->response = UA;
			break;
		case DISCPENDING:
			axp->response = DM;
			break;
		case SETUP:
			axp->response = UA;
			break;
		}
} else {
		polled = NO;		/* DG2KK */
}
		break;
	case UA:
		axp->retries = 0;
		switch(axp->state){
		case CONNECTED:
			lapbstate(axp,SETUP);
			sendctl(axp,COMMAND,SABM|PF);
			break;
		case SETUP:
			stop_timer(&axp->t1);
			lapbstate(axp,CONNECTED);
			break;
		case DISCPENDING:
			lapbstate(axp,DISCONNECTED);
			break;
		/* note - ignored if DISCONNECTED or in FRAMEREJECT state */
		}
		break;
	case DISC:
		switch(axp->state){
		case SETUP:
			lapbstate(axp,DISCONNECTED);
			axp->response = DM;
			break;
		case DISCPENDING:
			axp->response = UA;
			break;
		default:
			axp->response = UA;
			lapbstate(axp,DISCONNECTED);
			break;
		}
		break;
	case RR:
		axp->remotebusy = NO;
		break;
	case RNR:
		axp->remotebusy = YES;
		axp->retries = 0;
		start_timer(&axp->t1);	/* Probe as long as necessary */
		break;
	case REJ:
		/* Crank back V(s) to start of queue */
		axp->vs -= axp->unack;
		axp->vs &= MMASK;
		axp->unack = 0;
		break;
	case I:
		if(len_mbuf(axp->rxq) >= axp->window){
			/* Too bad he didn't listen to us; he'll
			 * have to resend the frame later. This
			 * drastic action is necessary to avoid
			 * deadlock.
			 */
			axp->response = RNR;
			free_p(bp);
			bp = NULLBUF;
			break;
		}
		/* Reject or ignore I-frames with receive sequence number errors */
		if(((control>>1) & MMASK) != axp->vr){
			if(axp->proto == V1 || !axp->rejsent){
				axp->rejsent = YES;
				axp->response = REJ;
			}
			break;
		}
		axp->rejsent = NO;
		axp->vr = (axp->vr+1) & MMASK;
		axp->response = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
		procdata(axp,bp);
		bp = NULLBUF;
		break;
	case FRMR:
		if(axp->state == FRAMEREJECT || axp->state == CONNECTED){
			lapbstate(axp,SETUP);
			sendctl(axp,COMMAND,SABM|PF);
		}
		break;
	case DM:
		switch(axp->state){
		case SETUP:
		case DISCPENDING:
			lapbstate(axp,DISCONNECTED);
			break;
		default:
			lapbstate(axp,SETUP);
			sendctl(axp,COMMAND,SABM|PF);
		}
		break;
	default:
		frej |= W;
		break;
	}
done:
	free_p(bp);	/* In case anything's left */

	/* Check if we have to make some sort of response to this frame */
	if(frej){
		/* Frame reject state, respond only with FRMR */
		frmr(axp,control,frej);
		return;
	}
	/* If we're being polled or have a U-frame response, respond
	 * immediately
	 */
	if(polled || (axp->response & 0x3) == U){
		if(axp->response == 0)
			axp->response = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
		sendctl(axp,RESPONSE,axp->response|PF);
		stop_timer(&axp->t2);	/* No need for delayed response */
		axp->response = 0;
	}
	/* See if we can send some data, perhaps piggybacking an ack.
	 * If successful, lapb_output will clear axp->response.
	 */
	lapb_output(axp);

	if(axp->response){
		/* We STILL owe him an ack. Try to delay it, but if T2 is
		 * disabled, send it right away
		 */
		if(axp->t2.start != 0){
			start_timer(&axp->t2);
		} else {
			sendctl(axp,RESPONSE,axp->response);
			axp->response = 0;
		}
	}
	/* Restart timer T1 if there are still unacknowledged I-frames or
	 * a poll outstanding.
	 */
	if(axp->state != DISCONNECTED && (axp->unack !=0 || axp->waitack))
		start_timer(&axp->t1);
	/* Start timer T3 (the idle poll timer) under AX.25 V2.0
	 * whenever T1 is stopped, unless we are disconnected
	 */
	if(axp->state != DISCONNECTED && axp->t1.state != TIMER_RUN
	 && axp->t3.start != 0 && axp->proto == V2)
		start_timer(&axp->t3);
	else
		stop_timer(&axp->t3);

	/* Empty the trash */
	if(axp->state == DISCONNECTED)
		del_ax25(axp);
}
/* Handle incoming acknowledgements for frames we've sent.
 * Free frames being acknowledged.
 * Return -1 to cause a frame reject if number is bad, 0 otherwise
 */
static
ackours(axp,n)
struct ax25_cb *axp;
char n;
{	
	struct mbuf *bp;
	int acked = 0;	/* Count of frames acked by this ACK */
	int oldest;	/* Seq number of oldest unacked I-frame */

	/* Free up acknowledged frames by purging frames from the I-frame
	 * transmit queue. Start at the remote end's last reported V(r)
	 * and keep going until we reach the new sequence number.
	 * If we try to free a null pointer,
	 * then we have a frame reject condition.
	 * Stop the T1 timer if at least one frame is being acknowledged;
	 * it will be restarted again if not all frames were acknowledged.
	 */
	oldest = (axp->vs - axp->unack) & MMASK;
	while(axp->unack != 0 && oldest != n){
		if((bp = dequeue(&axp->txq)) == NULLBUF){
			/* Acking unsent frame */
			return -1;
		}
		free_p(bp);
		axp->unack--;
		acked++;
		stop_timer(&axp->t1);
		axp->retries = 0;
		oldest = (oldest + 1) & MMASK;
	}
	/* If user has set a transmit upcall, indicate how many frames
	 * may be queued
	 */
	if(acked != 0 && axp->t_upcall != NULLVFP)
		(*axp->t_upcall)(axp,axp->paclen * (axp->maxframe - axp->unack));

	return 0;
}

/* Generate Frame Reject (FRMR) response
 * If reason != 0, this is the initial error frame
 * If reason == 0, resend the last error frame
 */
frmr(axp,control,reason)
register struct ax25_cb *axp;
char control;
char reason;
{
	struct mbuf *frmrinfo;
	register char *cp;

	if(reason != 0){
		cp = axp->frmrinfo;
		*cp++ = control;
		*cp++ =  axp->vr << 5 || axp->vs << 1;
		*cp = reason;
	}
	lapbstate(axp,FRAMEREJECT);
	if((frmrinfo = alloc_mbuf(3)) == NULLBUF)
		return;	/* No memory */
	frmrinfo->cnt = 3;
	memcpy(frmrinfo->data,axp->frmrinfo,3);
	sendframe(axp,RESPONSE,FRMR|(control&PF),frmrinfo);
}

/* Send S or U frame to currently connected station */
sendctl(axp,cmdrsp,cmd)
struct ax25_cb *axp;
char cmdrsp,cmd;
{
	int16 ftype();

	if((ftype(cmd) & 0x3) == S)	/* Insert V(R) if S frame */
		cmd |= (axp->vr << 5);
	sendframe(axp,cmdrsp,cmd,NULLBUF);
}
/* Start data transmission on link, if possible
 * Return number of frames sent
 */
int
lapb_output(axp)
register struct ax25_cb *axp;
{
	register struct mbuf *bp;
	struct mbuf *tbp;
	char control;
	int sent = 0;
	int i;

	if(axp == NULLAX25 || axp->state != CONNECTED || axp->remotebusy)
		return 0;

	/* Dig into the send queue for the first unsent frame */
	bp = axp->txq;
	for(i = 0; i < axp->unack; i++){
		if(bp == NULLBUF)
			break;	/* Nothing to do */
		bp = bp->anext;
	}
	/* Start at first unsent I-frame, stop when either the
	 * number of unacknowledged frames reaches the maxframe limit,
	 * or when there are no more frames to send
	 */
	while(bp != NULLBUF && axp->unack < axp->maxframe){
		control = I | (axp->vs++ << 1) | (axp->vr << 5);
		axp->vs &= MMASK;
		dup_p(&tbp,bp,0,len_mbuf(bp));
		if(tbp == NULLBUF)
			return sent;	/* Probably out of memory */
		/* We're implicitly acking any data he's sent, so stop any
		 * delayed ack
		 */
		axp->response = 0;
		stop_timer(&axp->t2);
		axp->unack++;
		sent++;
		bp = bp->anext;

		sendframe(axp,COMMAND,control,tbp);
	}
	return sent;
}
/* Set new link state
 * This is a major upheaval, so reset the world,
 * flush the I-queue. If the new state is disconnected,
 * also free the link control block
 */
lapbstate(axp,s)
struct ax25_cb *axp;
int s;
{
	int oldstate;

	oldstate = axp->state;
	axp->state = s;
	axp->rejsent = NO;
	axp->remotebusy = NO;
	axp->waitack = NO;
	axp->unack = axp->vr = axp->vs = 0;
	axp->retries = 0;
	if(s == DISCONNECTED){
		stop_timer(&axp->t1);
		stop_timer(&axp->t2);
		stop_timer(&axp->t3);
		free_q(&axp->txq);
	}
	if(axp->s_upcall != NULLVFP)
		(*axp->s_upcall)(axp,oldstate,axp->state);
}
/* Process a valid incoming I frame */
static
procdata(axp,bp)
struct ax25_cb *axp;
struct mbuf *bp;
{
	char pid;
	void ip_route(),nr_route();	/* DG2KK */

	/* Extract level 3 PID */
	if(pullup(&bp,&pid,1) != 1)
		return;	/* No PID */

	switch(pid & (PID_FIRST|PID_LAST)){
	case PID_FIRST:
		/* "Shouldn't happen", but flush any accumulated frags */
		free_p(axp->rxasm);
		axp->rxasm = NULLBUF;
	case 0:	/* Note fall-thru */
		/* Beginning or middle of message, just accumulate */
		append(&axp->rxasm,bp);
		return;
	case PID_LAST:
		/* Last frame of multi-frame message; extract it */
		append(&axp->rxasm,bp);
		bp = axp->rxasm;
		axp->rxasm = NULLBUF;
		break;
	case PID_FIRST|PID_LAST:
		/* Do nothing with reassembly queue, allowing single-frame
		 * messages to be interspersed with fragments of multi-frame
		 * messages
		 */
		break;
	}
	/* Last frame in sequence; kick entire message upstairs */
	switch(pid & PID_PID){
	case PID_IP:		/* DoD Internet Protocol */
		ip_route(bp,0);
		break;
	case PID_NO_L3:		/* Enqueue for application */
		append(&axp->rxq,bp);
		if(axp->r_upcall != NULLVFP)
			(*axp->r_upcall)(axp,len_mbuf(axp->rxq));
		break;
	case PID_NETROM:	/* DG2KK: from netrom version */
		nr_route(bp);
		break;	
	default:		/* Note: ARP is invalid here */	
		free_p(bp);
		break;			
	}
}

