/* 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"

/* Process incoming frames */
int
lapb_input(axp,cmdrsp,bp)
register struct ax25_cb *axp;   /* Link control structure */
char cmdrsp;                    /* Command/response flag */
struct mbuf *bp;                /* Rest of frame, starting with ctl */
{
        char control,p_control;
        char frej = 0;          /* FRMR reject reason code */
        char class;             /* General class (I/S/U) of frame */
        int16 type,p_type;      /* Specific type (I/RR/RNR/etc) of frame */
        char pf;                /* extracted poll/final bit */
        char vr;                /* extracted V(R) for I-frame */
        char polled = 0;
        char csum;              /* checksum for sammler */
        int16 ftype();

        if(bp == NULLBUF || axp == NULLAX25){
                free_p(bp);
                return -1;
        }
        p_control = axp->control;
        axp->control = 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->vs - axp->unack) & MMASK;
                axp->unack = 0;
                axp->control = REJ;     /* dirty: lapb_output sends single frame */
        }
        /* 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 */
                switch(axp->state){
                case DISCONNECTED:
                        lapbstate(axp,CONNECTED,LAPBCONN);
                        /* state-change upcall can force a DM by setting the
                           state to DISCONNECTED */
                        axp->response = (axp->state == CONNECTED)? UA : DM;
                        break;
                case FRAMEREJECT:
                        lapbstate(axp,CONNECTED,LAPBRESF);
                        axp->response = UA;
                        break;
                case CONNECTED:
                        lapbstate(axp,CONNECTED,LAPBNOMS);
                        axp->response = UA;
                        break;
                case DISCPENDING:
                        axp->response = DM;
                        break;
                case SETUP:
                        axp->response = UA;
                        break;
                }
                break;
        case UA:
                axp->retries = 0;
                switch(axp->state){
                case CONNECTED:
                        /* ignore an UA just after SABM or UA */
                        if ((p_type = ftype(p_control)) != UA &&
                             p_type != SABM){
                            lapbstate(axp,SETUP,LAPBNOMS);
                            sendctl(axp,COMMAND,SABM|PF);
                        }
                        break;
                case SETUP:
                        stop_timer(&axp->t1);
                        lapbstate(axp,CONNECTED,LAPBCONN);
                        break;
                case DISCPENDING:
                        lapbstate(axp,DISCONNECTED,LAPBDISC);
                        break;
                /* note - ignored if DISCONNECTED or in FRAMEREJECT state */
                }
                break;
        case DISC:
                switch(axp->state){
                case SETUP:
                        lapbstate(axp,DISCONNECTED,LAPBDISC);
                        axp->response = DM;
                        break;
                case DISCPENDING:
                        axp->response = UA;
                        break;
                default:
                        lapbstate(axp,DISCONNECTED,LAPBDISC);
                        axp->response = UA;
                        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->vs - axp->unack) & MMASK;
                axp->unack = 0;
                break;
        case I:
                if (axp->state != CONNECTED){   /* apparently missed UA */
                        if (!run_timer(&axp->t1)) /* make sure timer is running */
                                start_timer(&axp->t1);
                        break;                  /* ignore the I-frame */
                }
                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;
                        break;
                }
                /* Extract V(R) and throw away the framesammler contents */
                /* (A received frame always overrides the framesammler) */
                free_p(axp->sammlq[vr = (control>>1) & MMASK]);
                axp->sammlq[vr] = NULLBUF;

                /* calculate a simple checksum over the frame. this is used */
                /* to discard retransmissions of the same frame when using  */
                /* the framesammler.  safety-first is the approach here.    */
                if (axp->maxsamml != 0 && bp != NULLBUF){
                    register struct mbuf *cbp;
                    register char *cdata;
                    register int ccnt;

                    csum = 0;
                    cbp = bp;

                    do {
                        cdata = cbp->data;
                        ccnt = cbp->cnt;
                        do {
                            csum += *cdata++;
                        } while (--ccnt);
                    } while ((cbp = cbp->next) != NULLBUF);
                }

                /* Reject or ignore I-frames with receive sequence number errors */
                if(vr != axp->vr){
                        /* Check if we can store it in the framesammler */
                        /* we won't store frames with checksum equal to */
                        /* an equal-numbered (earlier) frame            */
                        if(axp->t2.start != 0 && csum != axp->sammlchk[vr] &&
                           ((vr - axp->vr) & MMASK) <= axp->maxsamml){
                                axp->sammlq[vr] = bp;
                                bp = NULLBUF;
                        }
                        if(axp->proto == V1 || !axp->rejsent){
                                axp->rejsent = YES;
                                axp->response = REJ;
                        }
                        break;
                }

                if (axp->maxsamml != 0)
                        axp->sammlchk[vr] = csum;
                axp->rejsent = NO;
                axp->vr = (axp->vr+1) & MMASK;
                axp->response = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
                start_timer(&axp->t4);  /* valid I frame, kick T4 timer */
        case UI:
                procdata(axp,bp);
                bp = NULLBUF;
                break;
        case FRMR:
                if(axp->state == FRAMEREJECT || axp->state == CONNECTED){
                        lapbstate(axp,SETUP,LAPBFRMF);
                        sendctl(axp,COMMAND,SABM|PF);
                }
                break;
        case DM:
                switch(axp->state){
                case SETUP:
                        lapbstate(axp,DISCONNECTED,LAPBBUSY);
                        break;
                case DISCPENDING:
                        lapbstate(axp,DISCONNECTED,LAPBDISC);
                        break;
                default:
                        lapbstate(axp,SETUP,LAPBRESF);
                        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 0;
        }
        /* If we're being polled or have a U-frame or REJ response, respond
         * immediately
         */
        if((polled && axp->state == CONNECTED) ||
           (axp->response & 0x3) == U || axp->response == REJ){
                if(axp->response == 0)
                        axp->response = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
                if(polled)
                        axp->response |= PF;
                sendctl(axp,RESPONSE,axp->response);
                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.
         * Don't send output when a REJ came in, wait for the T1 timer.
         * (this is done to avoid problems when multiple REJ's are in the
         *  pipeline.  When all sent frames are acked, proceed.)
         */
        if(type != REJ || axp->sent == 0 || (pf && cmdrsp == 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;
                }
        }
        if(axp->state != DISCONNECTED){
            /* Restart timer T1 if there are still unacknowledged I-frames or
             * a poll outstanding.
             */
            if(axp->sent != 0 || axp->waitack){
                start_timer(&axp->t1);
            } else {
                if(axp->fin && axp->txq == NULLBUF && axp->state != DISCPENDING)
                    disc_ax25(axp);     /* FIN and all acked, DISCONNECT */
            }
        }
        /* 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);
        return 0;
}
/* 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)
register struct ax25_cb *axp;
char n;
{
        struct mbuf *bp;
        int acked = 0;  /* Count of frames acked by this ACK */

        /* 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.
         */
        while(axp->sent != 0 && ((axp->vs - axp->unack) & MMASK) != n){
                if((bp = dequeue(&axp->txq)) == NULLBUF){
                        /* Acking unsent frame */
                        return -1;
                }
                free_p(bp);
                axp->sent--;                    /* one sent frame acked */
                if(axp->unack != 0)             /* acking unacked frame? */
                        axp->unack--;           /* one less unacked */
                else                            /* else acks beyond prev REJ */
                        axp->vs = (axp->vs + 1) & MMASK;
                acked++;
        }
        if(acked != 0){
            stop_timer(&axp->t1);
            axp->retries = 0;
            start_timer(&axp->t4);      /* something acked, kick T4 timer */

            /* If user has set a transmit upcall, indicate how many frames
             * may be queued
             */
            if(axp->t_upcall != NULLVFP && len_q(axp->txq) < axp->maxframe)
                (*axp->t_upcall)(axp,axp->paclen * (axp->maxframe - len_q(axp->txq)));
        }
        return 0;
}

/* Generate Frame Reject (FRMR) response
 * If reason != 0, this is the initial error frame
 * If reason == 0, resend the last error frame
 */
int
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,LAPBFRMT);
        if((frmrinfo = alloc_mbuf(3)) == NULLBUF)
                return -1;      /* No memory */
        frmrinfo->cnt = 3;
        memcpy(frmrinfo->data,axp->frmrinfo,3);
        return sendframe(axp,RESPONSE,FRMR|(control&PF),frmrinfo);
}

/* Send S or U frame to currently connected station */
int
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);
        return 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;
        int16 rxtype,ftype();

        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;
        }
        rxtype = ftype(axp->control);   /* type of last received frame */

        /* 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;
                if((tbp = copy_p(bp,len_mbuf(bp))) == 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);
                if (++axp->unack > axp->sent)
                        axp->sent = axp->unack;
                sent++;
                bp = bp->anext;

                sendframe(axp,COMMAND,control,tbp);

                if (rxtype == REJ)      /* after a REJ send only one frame */
                        break;          /* to allow selective rejecting */
        }
        return sent;
}
/* Set new link state
 * This is a major upheaval, so reset the world,
 * flush the I-queue and the Framesammler. If the new state is disconnected,
 * also free the transmit queue
 */
lapbstate(axp,s,msg)
register struct ax25_cb *axp;
int s,msg;
{
        int oldstate,i;

        oldstate = axp->state;
        axp->state = s;
        axp->rejsent = NO;
        axp->remotebusy = NO;
        axp->waitack = NO;
        axp->sent = axp->unack = axp->vr = axp->vs = 0;
        axp->retries = 0;
        for(i = 0; i < 8; i++){
                free_p(axp->sammlq[i]);
                axp->sammlq[i] = NULLBUF;
        }
        switch(s)
        {
        case CONNECTED:
                start_timer(&axp->t4);
                break;

        case DISCONNECTED:
                stop_timer(&axp->t1);
                stop_timer(&axp->t2);
                stop_timer(&axp->t3);
                stop_timer(&axp->t4);
                free_q(&axp->txq);
                break;

        case SETUP:
        case DISCPENDING:
                axp->response = 0;
                break;
        }
        if((oldstate != axp->state || msg != LAPBNOMS) &&
            axp->s_upcall != NULLVFP)
                (*axp->s_upcall)(axp,oldstate,axp->state,msg);
}
/* Process a valid incoming I frame */
procdata(axp,bp)
register struct ax25_cb *axp;
struct mbuf *bp;
{
        char pid;
        void ip_route();

        /* 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_NETROM:                /* NET/ROM inter-node */
                nr_route(axp,bp);       /* send to it's router */
                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;
        default:                        /* Note: ARP is invalid here */
                free_p(bp);
                break;
        }
}

