home *** CD-ROM | disk | FTP | other *** search
- /*
- AX25LABP.C -- Implements AX.25 link level routines
-
- Poor Man's Packet (PMP)
- Copyright (c) 1991 by Andrew C. Payne All Rights Reserved.
-
- Permission to use, copy, modify, and distribute this software and its
- documentation without fee for NON-COMMERCIAL AMATEUR RADIO USE ONLY is hereby
- granted, provided that the above copyright notice appear in all copies.
- The author makes no representations about the suitability of this software
- for any purpose. It is provided "as is" without express or implied warranty.
-
- August, 1989
- Andrew C. Payne
- */
-
- /* ----- Includes ----- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <conio.h>
- #include <bios.h>
- #include <mem.h>
- #include <alloc.h>
- #include <string.h>
- #include "pmp.h"
-
- static void EmptyDQ(void);
-
- /* ----- Local Variables ----- */
-
- static struct ax25_packet *AX25_TXQueue[8]; /* TX Queue */
-
- #define INC(x) (((x)+1) & 7) /* mod 7 increment */
- #define DEC(x) (((x)-1) & 7) /* mod 7 decrement */
-
- #define SECONDS(x) (BiosTime() + (x) * BIOSSEC)
-
- /* ----- Timer Stuff ----- */
-
- /* StartT1()
- Starts timer T1 going. The T1 timeout is calculated from the
- FRACK parameter, the number of digis in the connect path, AND the
- number of bytes currently sitting in the TX queue (so that the
- timer starts up *after* the TX contents are sent).
- */
- void StartT1(void)
- {
- AX25_Control.t1 = SECONDS(Frack*(2 * AX25_Control.header.ndigis + 1)) +
- TXQBytesInQ() * 8 / 1200 * BIOSSEC;
- }
-
- /* StopT1()
- Stops timer T1.
- */
- #define StopT1() (AX25_Control.t1 = 0)
-
- /* StartT3()
- Starts timer T3 (keep-alive).
- */
- static void StartT3(void)
- {
- AX25_Control.t3 = SECONDS(Check);
- }
-
- /* StopT3()
- Stops timer T3.
- */
- #define StopT3() (AX25_Control.t3 = 0)
-
- /* Connected()
- Returns TRUE if the link is in the connected (information xfer)
- state.
- */
- int Connected(void)
- {
- switch(AX25_Control.state) {
- case CONNECTED:
- case RECOVERY:
- return TRUE;
- default:
- return FALSE;
- }
- }
-
- /* DumpState()
- Shows the state of the TXQ on the status line (sort of like an
- expanded 'STA' light).
-
- This routine was originally for debugging, but it is kind of neat,
- so I'm leaving it in.
- */
- static void DumpState(void)
- {
- int i;
- char s[10];
- static char blank[] = " ";
-
- /* go thorough all 8 TXQ entries */
- if(Connected()) {
- for(i=0; i<8; i++)
- if(AX25_TXQueue[i] != NULL)
- s[i] = '*';
- else
- s[i] = '-';
- } else
- strcpy(s,blank);
-
- /* show the string on the status line */
- putstring(64,25,8,0x70,s);
- }
-
- /* ----- Subroutines ----- */
-
- /* AX25_Init()
- Initializes the AX25 stuff in this module.
- */
- void AX25_Init(void)
- {
- int i;
-
- for(i=0; i<8; i++)
- AX25_TXQueue[i] = NULL;
- }
-
- /* RecACK(n)
- Given a sequence number, acknowledges all packets up to, but not
- including that number.
-
- If all frames are ACKed, T1 is axed, else T1 is restarted.
- */
- static void RecACK(int n)
- {
- int i;
- int ackcount;
-
- /* go backwards through the AX25_TXQueue freeing ACKed packets */
- ackcount = 0;
- i = DEC(n);
- while(AX25_TXQueue[i] != NULL) { /* was ACKed */
- free(AX25_TXQueue[i]);
- AX25_TXQueue[i] = NULL;
- i = DEC(i);
- ackcount++;
- AX25_Control.unack--;
- AX25_Control.qsize--;
- }
-
- if(ackcount)
- AX25_Control.retries = 0; /* reset retries */
-
- /* adjust T1 */
- if(AX25_Control.unack == 0) {
- StopT1(); /* no outstanding */
- StartT3();
- } else if(ackcount)
- StartT1(); /* restart T1 */
-
- DumpState();
- }
-
- /* FlushTXQ()
- Discards any entries in the TX Queue. Done after a disconnect and
- before a connect.
- */
- static void FlushTXQ(void)
- {
- int i;
-
- /* free up any non-NULL entries */
- for(i=0; i<8; i++) {
- if(AX25_TXQueue[i] != NULL) {
- free(AX25_TXQueue[i]);
- AX25_TXQueue[i] = NULL;
- }
- }
- AX25_Control.qsize = AX25_Control.unack = 0;
-
- DumpState();
- }
-
- /* SendControl(cmd,cmdresp)
- Sends a command/response packet to the current destination specified
- in the AX25_Control record.
-
- If there is a pending ACK, it is cleared.
- */
- static void SendControl(int cmd, int cmdresp)
- {
- /* add rec sequence to S packets */
- if((cmd & 3) == 1) {
- AX25_Control.response = 0;
- cmd |= AX25_Control.vr << 5;
- }
-
- /* set up the header */
- AX25_Control.header.dlen = 0;
- AX25_Control.header.cont = cmd;
- AX25_Control.header.cmdresp = cmdresp;
-
- /* enqueue packet */
- SendAX25(&AX25_Control.header);
- }
-
- /* ReplyDM(p)
- Given an incoming packet (with path), sends a Disconnected Mode
- reply.
- */
- static void ReplyDM(struct ax25_packet *p)
- {
- struct ax25_packet p1;
-
- ReversePath(&p1,p);
- p1.dlen = 0;
- p1.cont = DM|PF;
- p1.cmdresp = RESPONSE;
-
- SendAX25(&p1);
- }
-
- /* ChangeState(s)
- Changes to the state given. Performs any processing needed for the
- state change.
-
- NOTE: if the change is to the CONNECTED state from any state but
- RECOVERY, the link is reset.
- */
- void ChangeState(int s)
- {
- /* already in this state?, if so, no change */
- if(AX25_Control.state == s)
- return;
-
- /* notify the user what's happening */
- NotifyStatus(AX25_Control.state, s);
-
- /* for connects and disconnects, initialize */
- if(s == DISCONNECTED || (s == CONNECTED && AX25_Control.state != RECOVERY)) {
- StopT1();
- StopT3();
- AX25_Control.vs = 0;
- AX25_Control.vr = 0;
- AX25_Control.tqs = 0;
- AX25_Control.response = 0;
- AX25_Control.retries = 0;
- AX25_Control.remotebusy = FALSE;
- DQInit(&AX25_Control.dq); /* init data queue */
- FlushTXQ(); /* flush queue */
- }
-
- /* set current state */
- AX25_Control.state = s;
- DumpState();
- }
-
- /* AX25_Open()
- Starts setting up an AX25 connection.
-
- NOTE: Assumes that the AX25_Control record has already been set up.
- */
- void AX25_Open(void)
- {
- switch(AX25_Control.state) {
- case DISCONNECTED:
- case SETUP:
- SendControl(SABM,COMMAND); /* send connect packet */
- StartT1();
- ChangeState(SETUP);
- break;
- }
- }
-
- /* ----- Incoming Packet Handling ----- */
-
- /* AX25_Output()
- Writes out any I frames sitting in the AX25_TXQueue, but that have
- not been transmitted.
-
- If an I-frame is sent, an implicit ACK is also sent, so the
- 'response' variable is cleared.
- */
- static void AX25_Output(void)
- {
- int i;
-
- /* should be in the CONNECTED or RECOVERY state and remote not busy */
- if(AX25_Control.state != CONNECTED && AX25_Control.state != RECOVERY
- && !AX25_Control.remotebusy)
- return;
-
- /* send any frames ahead of us in the TX queue */
- i = AX25_Control.vs; /* next frame to send */
- while(AX25_TXQueue[i] != NULL) {
-
- /* set up the control field and send the packet */
- AX25_TXQueue[i]->cont = (i << 1) | (AX25_Control.vr << 5) | I;
- SendAX25(AX25_TXQueue[i]);
-
- /* the ack was piggybacked on the I frame... */
- AX25_Control.response = 0;
-
- i = INC(i);
- AX25_Control.unack++;
-
- /* if T1 is not running, start it up */
- if(AX25_Control.t1 == 0) {
- StartT1();
- StopT3();
- }
- }
- AX25_Control.vs = i;
- }
-
- /* AX25_Flush()
- Called once at the end of each RXQueue pass. This routine sends
- any enqueued I packets (if the remote is not busy) and any queued
- response.
- */
- void AX25_Flush(void)
- {
- /* write any pending output */
- EmptyDQ(); /* copy from DQ */
- AX25_Output();
-
- /* if a pending ack left to send, send it */
- if(AX25_Control.response)
- SendControl(AX25_Control.response,RESPONSE);
- }
-
- /* upcall(p, len)
- Called for each valid incoming packet.
- */
- static void upcall(byte *p, int len)
- {
- #ifdef REMOTE
- char s[80];
- #endif
- eol_in(EOL_CR, p, len); /* convert EOLs */
- uputtext(NormalAttr, p, len);
-
- #ifdef REMOTE
- if(p[0] == '/' && p[1] == '/') {
- memcpy(s, p, len);
- s[len] = '\0'; /* terminate */
- command(s+2);
- }
- #endif
- }
-
- /* AX25_Incoming(p)
- Called with a pointer to a level 2 packet for each incoming packet
- addressed to us.
- */
- void AX25_Incoming(struct ax25_packet *p2)
- {
- int ftype; /* frame type */
- int poll; /* command, poll? */
- int final; /* reply, final? */
- int nr; /* rec'd incoming frames */
- int ns;
- int other; /* TRUE if from other station */
-
- other = !CompAX25Addr(&p2->source, &AX25_Control.header.dest);
- ftype = FrameType(p2->cont); /* get frame type */
-
- /* set up the poll or final flags */
- poll = final = FALSE;
- if(p2->cont & PF) {
- if(p2->cmdresp == COMMAND)
- poll = TRUE;
- else
- final = TRUE;
- }
-
- /* get the send/rec sequences for later use */
- nr = (p2->cont >> 5) & 7;
- ns = (p2->cont >> 1) & 7;
-
- /* increment the RX counters */
- switch(ftype) {
- case REJ:
- RXREJ++;
- break;
- case FRMR:
- RXFRMR++;
- break;
- }
-
- /* trap stray packets while connected */
- if(AX25_Control.state != DISCONNECTED && !other) {
- ReplyDM(p2);
- return;
- }
-
- /* handle the packet, depending on the mode we are in */
- switch(AX25_Control.state) {
-
- /* disconnected mode */
- case DISCONNECTED:
- switch(ftype) {
- case SABM: /* incoming connect */
- ReversePath(&AX25_Control.header,p2);
- AX25_Control.header.pid = PID_TEXT;
- ChangeState(CONNECTED);
- SendControl(UA|PF,RESPONSE);
- SendWelcome();
- StartT3();
- break;
- case DM:
- break;
- default: /* don't bother us! */
- if(poll)
- ReplyDM(p2);
- break;
- }
- break;
-
- /* link setup mode */
- case SETUP:
- switch(ftype) {
- case SABM: /* simultaneous connect */
- SendControl(UA|PF,RESPONSE);
- ChangeState(CONNECTED);
- SendWelcome();
- StartT3();
- break;
- case DISC: /* disconnect */
- SendControl(DM|PF,RESPONSE);
- AX25_Control.dreason = DISC_BUSY;
- ChangeState(DISCONNECTED);
- break;
- case UA: /* connection accept */
- ChangeState(CONNECTED);
- StartT3();
- break;
- case DM: /* connection refused */
- AX25_Control.dreason = DISC_BUSY;
- ChangeState(DISCONNECTED);
- break;
- default:
- break; /* ignore */
- }
- break;
-
- /* disconnect pending mode */
- case DISCONNECTPEND:
- switch(ftype) {
- case SABM:
- SendControl(DM|PF,RESPONSE);
- break;
- case DISC:
- SendControl(UA|PF,RESPONSE);
- break;
- case UA:
- case DM:
- AX25_Control.dreason = DISC_LOCAL;
- ChangeState(DISCONNECTED);
- break;
- default:
- if(poll) {
- SendControl(DM|PF,RESPONSE);
- AX25_Control.dreason = DISC_LOCAL;
- ChangeState(DISCONNECTED);
- }
- break;
- }
- break;
-
- /* "we are connected" mode */
- case CONNECTED:
- switch(ftype) {
- case SABM: /* link reset */
- SendControl(UA|PF,RESPONSE);
- ChangeState(CONNECTED);
- StartT3();
- break;
- case DISC:
- SendControl(UA|PF,RESPONSE);
- AX25_Control.dreason = DISC_REMOTE;
- ChangeState(DISCONNECTED);
- break;
- case DM:
- AX25_Control.dreason = DISC_REMOTE;
- ChangeState(DISCONNECTED);
- break;
- case UA:
- AX25_Control.remotebusy = FALSE;
- /* ignore */
- break;
- case FRMR: /* frame reject */
- uprintf(InvAttr,"** Fatal -- Frame reject received. Resetting link...\n");
- ChangeState(SETUP);
- AX25_Open(); /* reset link */
- break;
- case REJ:
- RecACK(nr); /* ACK our frames */
- if(poll)
- SendControl(RR|PF,RESPONSE);
- StopT1(); /* re-xmit */
- AX25_Control.vs -= AX25_Control.unack;
- AX25_Control.vs &= 7;
- AX25_Control.unack = 0;
- AX25_Control.remotebusy = FALSE;
- StartT3();
- break;
- case RNR:
- case RR:
- AX25_Control.remotebusy = (ftype == RNR);
- RecACK(nr); /* ACK our frames */
- if(poll) /* respond to polls */
- SendControl(RR|PF,RESPONSE);
- break;
- case I:
- RecACK(nr); /* ACK our frames */
- if(ns == AX25_Control.vr) { /* in sequence? */
- upcall(p2->data, p2->dlen);
- AX25_Control.vr = INC(AX25_Control.vr);
- if(poll)
- SendControl(RR|PF, RESPONSE);
- else
- AX25_Control.response = RR;
- } else { /* out of seq */
- if(poll)
- SendControl(REJ|PF, RESPONSE);
- else
- AX25_Control.response = REJ;
- }
- }
- break;
-
- /* "can we get out of this mess?" mode */
- case RECOVERY:
- switch(ftype) {
- case SABM: /* link reset */
- SendControl(UA|PF,RESPONSE);
- ChangeState(CONNECTED);
- StartT3();
- break;
- case DISC:
- SendControl(UA,RESPONSE);
- AX25_Control.dreason = DISC_REMOTE;
- ChangeState(DISCONNECTED);
- break;
- case DM:
- AX25_Control.dreason = DISC_REMOTE;
- ChangeState(DISCONNECTED);
- break;
- case UA:
- /* ignore */
- AX25_Control.remotebusy = FALSE;
- break;
- case FRMR:
- uprintf(InvAttr,"** Fatal -- Frame reject received. Resetting link...\n");
- ChangeState(SETUP);
- AX25_Open(); /* reset link */
- break;
- case RNR:
- case RR:
- AX25_Control.remotebusy = (ftype == RNR);
- RecACK(nr);
- if(final) { /* got a reply */
- StopT1();
- if(AX25_Control.unack) {
- AX25_Control.vs -= AX25_Control.unack;
- AX25_Control.vs &= 7;
- AX25_Control.unack = 0;
- } else {
- ChangeState(CONNECTED);
- StartT3();
- }
- } else {
- if(poll) /* respond to poll */
- SendControl(RR|PF,RESPONSE);
- if(AX25_Control.t1 == 0)
- StartT1();
- }
- break;
- case REJ:
- RecACK(nr);
- if(final) {
- StopT1();
- if(AX25_Control.unack) {
- AX25_Control.vs -= AX25_Control.unack;
- AX25_Control.vs &= 7;
- AX25_Control.unack = 0;
- } else {
- ChangeState(CONNECTED);
- StartT3();
- }
- } else {
- if(poll) /* enqueue reply */
- SendControl(RR|PF,RESPONSE);
- if(AX25_Control.unack) {
- AX25_Control.vs -= AX25_Control.unack;
- AX25_Control.vs &= 7;
- AX25_Control.unack = 0;
- }
- if(AX25_Control.t1 == 0)
- StartT1();
- }
- AX25_Control.remotebusy = FALSE;
- break;
- case I:
- RecACK(nr);
-
- if(AX25_Control.t1 == 0) /* keep T1 */
- StartT1();
- if(ns == AX25_Control.vr) { /* in sequence? */
- upcall(p2->data, p2->dlen);
- AX25_Control.vr = INC(AX25_Control.vr);
- if(poll)
- SendControl(RR|PF, RESPONSE);
- else
- AX25_Control.response = RR;
- } else { /* out of seq */
- if(poll)
- SendControl(REJ|PF, RESPONSE);
- else
- AX25_Control.response = REJ;
- }
- break;
- }
- break;
- }
- }
-
- /* AX25_Close()
- Start closing an AX25 connection.
- */
- void AX25_Close(void)
- {
- switch(AX25_Control.state) {
- case DISCONNECTED: /* ignore */
- break;
- case DISCONNECTPEND:
- AX25_Control.dreason = DISC_LOCAL;
- ChangeState(DISCONNECTED);
- break;
- case CONNECTED: /* start disconnect */
- case RECOVERY:
- case SETUP:
- AX25_Control.retries = 0;
- SendControl(DISC,COMMAND);
- StartT1();
- ChangeState(DISCONNECTPEND);
- break;
- }
- }
-
- /* PollRemote()
- Called when T1 times out in CONNECTED or RECOVERY mode to poll
- the remote DXE.
- */
- static void PollRemote(void)
- {
- int i;
-
- /* if data sitting in the queue, use it */
- if(AX25_Control.unack) {
- i = (AX25_Control.vs - AX25_Control.unack) & 0x7;
- if(AX25_TXQueue[i]->dlen <= Pthresh) {
-
- /* set up the control field and send the packet */
- AX25_TXQueue[i]->cont = (i << 1) | (AX25_Control.vr << 5) | I | PF;
- SendAX25(AX25_TXQueue[i]);
- StartT1();
- return;
- }
- }
-
- /* else, just send RR w/ poll */
- SendControl(RR|PF,COMMAND);
- StartT1();
- }
-
- /* T1Expire()
- Gets called when T1 or T3 times out.
- */
- static void T1Expire(void)
- {
- switch(AX25_Control.state) {
- case SETUP: /* link setup */
- if(Retry != 0 && AX25_Control.retries >= Retry) {
- AX25_Control.dreason = DISC_TIMEOUT;
- ChangeState(DISCONNECTED);
- } else {
- AX25_Control.retries++;
- SendControl(SABM|PF,COMMAND);
- StartT1();
- }
- break;
- case DISCONNECTPEND: /* disconnect pending */
- if(Retry != 0 && AX25_Control.retries >= Retry) {
- AX25_Control.dreason = DISC_TIMEOUT;
- ChangeState(DISCONNECTED);
- } else {
- AX25_Control.retries++;
- SendControl(DISC|PF,COMMAND);
- StartT1();
- }
- break;
- case CONNECTED:
- AX25_Control.retries = 0;
- case RECOVERY: /* poll them */
- if(Retry != 0 && AX25_Control.retries >= Retry) {
- SendControl(DM|PF,RESPONSE);
- AX25_Control.dreason = DISC_TIMEOUT;
- ChangeState(DISCONNECTED);
- } else {
- AX25_Control.retries++;
- PollRemote();
- ChangeState(RECOVERY);
- }
- break;
- }
- }
-
- /* AX25_Expire(n)
- This routine gets called when one of the timers expires.
- 'n' is the timer number.
- */
- void AX25_Expire(int n)
- {
- switch(n) {
- case 1:
- StopT1();
- T1Expire();
- break;
- case 2:
- AX25_Control.t2 = 0;
- break;
- case 3:
- StopT3();
- T1Expire();
- break;
- }
- }
-
- /* ----- Link Transmission ----- */
-
- /* AX25QFull()
- Returns TRUE if the AX.25 send queue is full (e.g. MaxFrames
- outstanding).
- */
- int AX25QFull(void)
- {
- return AX25_Control.qsize >= MaxFrame;
- }
-
- /* LinkSend(p,len)
- Sends data across a connected AX.25 link. If text link, performs
- appropriate EOL conversions.
-
- Returns TRUE if error (not connected).
- */
- int LinkSend(byte *p, int len)
- {
- struct dqentry *dq;
-
- /* gotta be connected to do this stuff */
- if(!Connected())
- return TRUE;
-
- DQAdd(&AX25_Control.dq, p, len);
-
- if(AX25_Control.type == TEXT) {
- dq = DQFirst(&AX25_Control.dq);
- eol_out(EOL_CR, dq->data, dq->len);
- }
-
- return FALSE; /* no errors */
- }
-
- /* EmptyDQ()
- Empties data, if any from the DQ into the AX.25 TX queue.
-
- Ultimately, will merge packets in an attempt to get maximum sized
- packets.
- */
- static void EmptyDQ(void)
- {
- struct ax25_packet *ap;
- int len;
-
- /* loop until AX.25 queue full or DQueue empty */
- while(!AX25QFull() && AX25_Control.dq.len) {
-
- /* build an AX.25 packet, as long as possible */
- len = min(AX25_Control.dq.len, Paclen);
- ap = malloc(sizeof(struct ax25_packet) + len);
- memcpy(ap,&AX25_Control.header,sizeof(struct ax25_packet));
- ap->dlen = len;
- ap->pid = PID_TEXT;
- ap->cmdresp = COMMAND;
- ap->cont = (AX25_Control.vr<<5) | (AX25_Control.tqs<<1) | I;
-
- /* fill AX.25 packet from DQueue */
- DQExtract(&AX25_Control.dq, ap->data, len);
-
- /* add the packet to the AX.25 queue */
- AX25_TXQueue[AX25_Control.tqs] = ap;
- AX25_Control.tqs = INC(AX25_Control.tqs);
- AX25_Control.qsize++;
- DumpState();
- }
- }