📄 lapb.c
字号:
/* * This code shows a simple LAP-B implementation for use with PPP * Numbered Mode (RFC 1663). It assumes a system interface like the * ahdlc.c module provided with this example. * * It is also assumed that packets sent and received pass through * callback functions and are simple buffers. Buffers are acquired * through "buffer_fetch" and released back to the system by a * "buffer_release" function. * * The user of this module is provided a simple queue interface using * lapp_send() and lapb_receive(). * * This code may be used for any purpose as long as the author's * copyright is cited in any source code distributed. This code is * also available electronically from the author's web site. * * http://people.ne.mediaone.net/carlson/ppp * * http://www.workingcode.com/ppp * * Copyright 1999 by James Carlson and Working Code. */#include <stdlib.h>#include <assert.h>#include "sysdep.h"#include "lapb.h"/* * Theoretically, at least, the operation mode (basic, extended, or * super) could be negotiated and could be different in each direction * as could be the window size. This is not done in standard LAP-B. * This code optionally supports both features. */#ifdef DEBUG_LAPB#include <stdio.h>#define DBG(x) printf x#else#define DBG(x) /* */#endifenum lapb_phase { phQueue, phQueueDrain, phInitial, phData, phDisconnecting, phDisconnected};#ifdef DEBUG_LAPBstatic const char *phstring[] = { "queue", "qdrain", "initial", "data", "disconnecting", "disconnected",};#endif#define DEF_T1PERIOD 1000#define DEF_T2MAX 5/* Retransmission and reassembly queues */struct lapb_queue { octet *buffer; uint16 length; uint16 qflags;};#define QF_SENDING 0x0001 /* Frame is being sent */#define QF_ACKED 0x0002 /* Frame has been acked */#define QF_SEND 0x0004 /* Needs to be sent */struct lapb_state { void *userstate; void (*rcvnonempty)(void *userstate); void (*xmthasroom)(void *userstate); enum lapb_phase phase; int t1period; /* Length of timer T1 in milliseconds */ uint16 t2max,t2ctr; /* T2; max T1 timeouts */ octet mode; /* 0=basic, 1=extended, 2=super */ octet cmdaddr; /* Address to use in sending commands */ octet crabit; /* Bit for sending responses or rcv command */ octet flags; /* Configuration flags */ uint16 vs,vr; /* Send/receive state variables */ uint16 lastnr; /* Last received NR value (ack number) */ uint16 txqsize; /* Transmit sequence number modulus */ uint16 rxqsize; /* Receive sequence number modulus */ uint16 txkval; /* Window size "k" for transmit */ uint16 rxkval; /* Window size "k" for receive */ uint16 tsending; /* Item currently being sent */ uint16 tnext; /* Next item to send */ uint16 rtail,rhead; /* Queue pointers (modulo k) */ uint16 ttail,thead; /* Queue pointers (modulo k) */ uint16 statflag; /* Status code transmission flags */ octet *discarding; /* Buffer to discard */ struct lapb_queue txpend; /* Pending transmit */ struct lapb_queue *rxq; /* Receive queue (length k+1) */ struct lapb_queue *txq; /* Transmit queue (length k+1) */};/* Test if given NS value is inside the window. */#define WINDOWOFFS(ns,vr,mod) (((ns)+(mod)-(vr))%(mod))#define INWINDOW(ns,vr,mod,k) (WINDOWOFFS(ns,vr,mod) < (k))#define ALLSET(fl,bits) (((fl)&(bits)) == (bits))/* * Command and response definitions. CRU_SABM is for basic (mod 8) * mode only, CRU_SABME is for extended (mod 128) mode only, and * CRU_SM is for super (mod 32768) mode only. CRU_SREJ is not used * with basic mode. All other commands and responses are always * valid. */#define CRI_I 0x00 /* Information; command only */#define CRS_RR 0x01 /* Receive Ready */#define CRS_RNR 0x05 /* Receive Not Ready */#define CRS_REJ 0x09 /* Reject */#define CRS_SREJ 0x0D /* Selective Reject; response only */#define CRU_SABM 0x2F /* Set Asynchronous Balanced Mode; cmd only */#define CRU_SABME 0x6F /* Set Asynchronous Balanced Mode Extended */#define CRU_SM 0xC3 /* Set Mode; command only */#define CRU_DISC 0x43 /* Disconnect; command only */#define CRU_DM 0x0F /* Disconnect Mode; response only */#define CRU_UA 0x63 /* Unnumbered Acknowledgement; response only */#define CRU_FRMR 0x87 /* Frame Reject -- response only */#define IS_I_TYPE(x) (((x)&1) == 0)#define IS_S_TYPE(x) (((x)&3) == 1)#define IS_U_TYPE(x) (((x)&3) == 3)#define ITYPE_MASK 0x01#define STYPE_MASK 0x0F#define UTYPE_MASK 0xEF/* Internal flags */#define LBF_PEERREADY 0x10 /* Peer is ready for receive */#define LBF_IMREADY 0x20 /* I'm ready to receive (internal) */#define LBF_PERMISSIVE 0x40 /* Permissive set-mode operation */#define LBF_USERREADY 0x80 /* User is ready *//* Flags for statflag */#define STF_NEEDSTATUS 0x0001 /* Need to transmit status frame */#define STF_PFBIT 0x0002 /* Status frame should have PF bit set */#define STF_XMITRUN 0x0004 /* Transmit is running */#define STF_NEEDUA 0x0008 /* Need to transmit UA frame */#define STF_UAFBIT 0x0010 /* UA frame should have F bit set */#define STF_NEEDSABM 0x0020 /* Need to send SABM now */#define STF_XMITFULL 0x0040 /* User filled transmit queue */#define STF_NEEDREJ 0x0080 /* Sequence number error detected */#define STF_NEEDVR 0x0100 /* Need to give new VR number to peer */#define STF_T1RUNNING 0x0200 /* Timer T1 is running */#define STF_NEEDDM 0x0400 /* Need to send DM */#define STF_NEEDDISC 0x0800 /* Need to send DISC */static int mode_modulus[] = { 8, 128, 32768, 32768 };static void t1handler(void *state);static voidinit_values(struct lapb_state *ls, struct lapb_queue *qp, int mode, int txkval, int rxkval, int flags){ memset(ls,'\0',sizeof(*ls)); memset(qp,'\0',(txkval+rxkval+2)*sizeof(struct lapb_queue)); ls->rxq = qp; ls->txq = qp+rxkval+1; if (mode == MODE_PERMISSIVE) ls->mode = MODE_SUPER, flags |= LBF_PERMISSIVE; else ls->mode = mode; if (mode == MODE_SUPER) flags |= LBF_TXSREJ | LBF_RXSREJ; else if (mode == MODE_BASIC) flags &= ~LBF_TXSREJ & ~LBF_RXSREJ; if (flags & LBF_MULTILINK) { ls->cmdaddr = 0x0F; ls->crabit = 0x08; } else { ls->cmdaddr = 0x03; ls->crabit = 0x02; } ls->flags = flags | LBF_PEERREADY | LBF_IMREADY | LBF_USERREADY; if (flags & LBF_ISDTE) ls->cmdaddr ^= ls->crabit; ls->phase = phQueue; ls->txkval = txkval; ls->rxkval = rxkval; ls->txqsize = ls->rxqsize = mode_modulus[mode];}/* * Create a LAP-B session in plain queue mode. Returns a state * structure pointer. */void *lapb_create(int mode, int txkval, int rxkval, int t1period, int t2max, int flags){ struct lapb_state *ls; int len; struct lapb_queue *qp; if (mode < MODE_BASIC || mode > MODE_PERMISSIVE) return NULL; len = mode_modulus[mode]; if (txkval >= len || rxkval >= len) return NULL; if (txkval <= 0) txkval = 1; if (rxkval <= 0) rxkval = 1; ls = (struct lapb_state *)malloc(sizeof(*ls)); if (ls == NULL) return NULL; len = (txkval+rxkval+2)*sizeof(struct lapb_queue); qp = (struct lapb_queue *)malloc(len); if (qp == NULL) { free(ls); return NULL; } init_values(ls,qp,mode,txkval,rxkval,flags); if (t1period <= 0) t1period = DEF_T1PERIOD; ls->t1period = t1period; if (t2max <= 0) t2max = DEF_T2MAX; ls->t2max = t2max; return (void *)ls;}static voidfree_all_packets(struct lapb_state *ls){ int idx; DBG(("freeing all packets on state %p\n",ls)); if (!(ls->txpend.qflags & QF_SENDING) && ls->txpend.buffer != NULL) { buffer_release(ls->txpend.buffer); ls->txpend.buffer = NULL; ls->txpend.qflags = 0; } /* Note that "k" in the LAP-B spec is modulus - 1, thus use <= here. */ for (idx = 0; idx <= ls->txkval; idx++) { if (ls->txq[idx].qflags & QF_SENDING) { ls->txpend = ls->txq[idx]; DBG(("moved packet from TX queue to pending.\n")); } else if (ls->txq[idx].buffer != NULL) buffer_release(ls->txq[idx].buffer); ls->txq[idx].buffer = NULL; ls->txq[idx].qflags = 0; } for (idx = 0; idx <= ls->rxkval; idx++) if (ls->rxq[idx].buffer != NULL) { buffer_release(ls->rxq[idx].buffer); ls->rxq[idx].buffer = NULL; ls->rxq[idx].qflags = 0; } ls->rtail = ls->rhead = 0; ls->ttail = ls->thead = 0;}voidlapb_destroy(void *statep){ struct lapb_state *ls = (struct lapb_state *)statep; DBG(("destroying state structure %p\n",ls)); if (ls != NULL) { free_all_packets(ls); free(ls->rxq); free(ls); }}voidlapb_handlers(void *statep, void *userstate, void (*rcvnonempty)(void *userstate), void (*xmthasroom)(void *userstate)){ struct lapb_state *ls = (struct lapb_state *)statep; DBG(("set handlers on %p\n",ls)); ls->userstate = userstate; ls->rcvnonempty = rcvnonempty; ls->xmthasroom = xmthasroom;}/* * This is called when we need an upcall from the HDLC driver below * us. As long as he's been told, we don't need to tell him again. */static voidneed_transmit(struct lapb_state *ls, int flags){ ls->statflag |= flags; if (!(ls->statflag & STF_XMITRUN)) { ls->statflag |= STF_XMITRUN; enable_transmit(); }}/* * Handle received NR value. As with TCP this acks all transmitted * frames up through NR-1. */static voidhandle_nr(struct lapb_state *ls, int nr){ int lastnr,tptr,tptrn,tnext; /* Check for duplicate */ lastnr = ls->lastnr; if (lastnr == nr) return; /* Cancel T1 timeout */ if (ls->statflag & STF_T1RUNNING) { ls->t2ctr = 0; ls->statflag &= ~STF_T1RUNNING; untimeout(t1handler,(void *)ls); } ls->lastnr = nr; DBG(("handle nr %d on %p; lastnr %d\n",nr,ls,lastnr)); tptr = ls->ttail; tnext = ls->tnext; /* Loop through acked buffers and free them. */ while (lastnr != nr) { /* Special handling in case HDLC is busy with this frame. */ if (ls->txq[tptr].qflags & QF_SENDING) { DBG(("buffer %p in transit marked as acked; queue position %d\n", ls->txq[tptr].buffer,tptr)); ls->discarding = ls->txq[tptr].buffer; } else if (ls->txq[tptr].buffer != NULL) { DBG(("freeing buffer on ack; queue position %d\n",tptr)); buffer_release(ls->txq[tptr].buffer); } ls->txq[tptr].buffer = NULL; ls->txq[tptr].qflags = 0; if ((tptrn = tptr+1) > ls->txkval) tptrn = 0; if (tptr == tnext) tnext = tptrn; tptr = tptrn; lastnr = (lastnr + 1) % ls->txqsize; } ls->tnext = tnext; /* * If we now have more transmit room and the sender above us had * stopped because we ran out of room, then give him an upcall * to tell him that he can start again. */ if (tptr != ls->ttail) { ls->ttail = tptr; if (ls->statflag & STF_XMITFULL) { ls->statflag &= ~STF_XMITFULL; if (ls->phase == phData && ls->xmthasroom != NULL) (*ls->xmthasroom)(ls->userstate); else DBG(("no room signaled; phase %s\n",phstring[ls->phase])); } }}/* * Mark given sequence numbers in transmit queue for retransmission. * * This is intentionally not highly optimized. */static voidmark_retransmit(struct lapb_state *ls, int nrstart, int nrend){ int tptr,tnext,lastnr; /* Loop through packets, find matches, and retransmit those */ lastnr = ls->lastnr; tnext = ls->tnext; tptr = ls->ttail; while (tptr != ls->thead) { if (tptr == tnext) tnext = -1; if (nrstart < 0 || lastnr == nrstart) { if (nrstart >= 0 && tnext >= 0) ls->tnext = tptr; nrstart = -1; if (ls->txq[tptr].buffer == NULL) DBG(("ignoring retransmit request of unsent NS %d\n",lastnr)); else { DBG(("marked retransmit of NS %d\n",lastnr)); ls->txq[tptr].qflags |= QF_SEND; need_transmit(ls,0); } } if (lastnr == nrend) break; if (++tptr > ls->txkval) tptr = 0; if (++lastnr >= ls->txqsize) lastnr = 0; }}/* * This is called by the operating system dependent timer library. * It handles a time-out on T1. */static voidt1handler(void *state){ struct lapb_state *ls = (struct lapb_state *)state; int ptr; DBG(("T1 timeout on %p\n",ls)); ls->statflag &= ~STF_T1RUNNING; if (++ls->t2ctr >= ls->t2max) { ls->phase = phDisconnected; DBG(("T2 expired; disconnected.\n")); } switch (ls->phase) { case phInitial: DBG(("timeout requesting SABM\n")); need_transmit(ls,STF_NEEDSABM); break; case phData: /* Retransmit on timeout. */ for (ptr = ls->rtail; ptr != ls->rhead;) { if (ls->rxq[ptr].buffer == NULL) { DBG(("timeout requesting REJ/SREJ\n")); need_transmit(ls,STF_NEEDREJ); break; } if (++ptr > ls->rxkval) ptr = 0; } if (ls->thead != ls->ttail) { ptr = (ls->lastnr+ls->tnext-ls->ttail+2*ls->txqsize-1)%ls->txqsize; DBG(("timeout requesting I retransmit of NS %d\n",ptr)); mark_retransmit(ls,ptr,ptr); } break; case phDisconnecting: need_transmit(ls,STF_NEEDDM); DBG(("timeout requesting DM\n")); break; default: return; }}/* * The application should call this routine if it wishes to block or * unblock input. The current state (0 for blocked, 1 for unblocked) * is returned. If the flag is <0, then the current state is not * modified. */intlapb_ready_state(void *statep, int flag){ struct lapb_state *ls = (struct lapb_state *)statep; if (flag == 0) ls->flags &= ~LBF_USERREADY; else if (flag > 0) ls->flags |= LBF_USERREADY; return (ls->flags & LBF_USERREADY) ? 1 : 0;}/* * This routine is called from the function that receives data from * the HDLC receive queue. It processes the data according to the * LAP-B protocol and sends output frames to the tx_queue for output * processing by the HDLC transmit routines. */voidlapb_input(void *statep, octet *buffer, int count){ struct lapb_state *ls = (struct lapb_state *)statep; int iscmd,ns,nr,crcode,pfbit,errbits,ilen,woffs; octet *bp; if (ls == NULL || buffer == NULL || count < 2) { /* Error; missing address and control */ if (buffer != NULL) buffer_release(buffer); return; } if (ls->phase == phQueue) goto simple_queue; /* First examine the address */ if (buffer[0] == ls->cmdaddr) { /* This is a response from the peer */ iscmd = 0; } else if (buffer[0] == (ls->cmdaddr^ls->crabit)) { /* This is a command from the peer */ iscmd = 1; } else if (buffer[0] == 0xFF && buffer[1] == 0x03) { /* This is a non-LAP-B PPP frame; peer may have restarted. */ if (ls->phase != phQueue) { ls->phase = phQueue; free_all_packets(ls); } simple_queue: DBG(("simple queue on %p\n",ls)); woffs = ls->rhead; nr = woffs + 1; if (nr > ls->rxkval) nr = 0; if (nr == ls->rtail)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -