ftp.nice.ch/pub/next/unix/communication/Alby.2.PPP.0.3.N.bs.tar.gz#/source/ppp_async.c

This is ppp_async.c in view mode; [Download] [Up]

/* ppp_async.c - Streams async functions Also does FCS
    Copyright (C) 1990  Brad K. Clements, All Rights Reserved
    fcstab and some ideas nicked from if_ppp.c from cmu. See copyright notice in if_ppp.h
     and Readme.streams
*/
#include <sys/types.h>

#include "ppp.h"
#if NPPP > 0
#define	STREAMS	1
#define	DEBUGS	1
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/dir.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/if_ppp.h>
#include <sys/ppp_str.h>

#ifdef	DEBUGS
#include <sys/syslog.h>
#define	DLOG(s,a)	if(ppp_async_debug) log(LOG_INFO, s, a)
int	ppp_async_debug=0;
#else
#define	DLOG(s)	{}
#endif

static	int	ppp_async_open(), ppp_async_close(), ppp_async_rput(), ppp_async_wput(),
		ppp_async_wsrv(), ppp_async_rsrv();

static 	struct	module_info	minfo ={
	0xabcd,"ppp_async",0, INFPSZ, 16384, 4096
};

static	struct	qinit	r_init = {
	ppp_async_rput, ppp_async_rsrv, ppp_async_open, ppp_async_close, NULL, &minfo, NULL
};
static	struct	qinit	w_init = {
	ppp_async_wput, ppp_async_wsrv, ppp_async_open, ppp_async_close, NULL, &minfo, NULL
};
struct	streamtab	ppp_asyncinfo = {
	&r_init, &w_init, NULL, NULL, NULL
};

/*
 * FCS lookup table as calculated by genfcstab.
 */
static u_short fcstab[256] = {
	0x0000,	0x1189,	0x2312,	0x329b,	0x4624,	0x57ad,	0x6536,	0x74bf,
	0x8c48,	0x9dc1,	0xaf5a,	0xbed3,	0xca6c,	0xdbe5,	0xe97e,	0xf8f7,
	0x1081,	0x0108,	0x3393,	0x221a,	0x56a5,	0x472c,	0x75b7,	0x643e,
	0x9cc9,	0x8d40,	0xbfdb,	0xae52,	0xdaed,	0xcb64,	0xf9ff,	0xe876,
	0x2102,	0x308b,	0x0210,	0x1399,	0x6726,	0x76af,	0x4434,	0x55bd,
	0xad4a,	0xbcc3,	0x8e58,	0x9fd1,	0xeb6e,	0xfae7,	0xc87c,	0xd9f5,
	0x3183,	0x200a,	0x1291,	0x0318,	0x77a7,	0x662e,	0x54b5,	0x453c,
	0xbdcb,	0xac42,	0x9ed9,	0x8f50,	0xfbef,	0xea66,	0xd8fd,	0xc974,
	0x4204,	0x538d,	0x6116,	0x709f,	0x0420,	0x15a9,	0x2732,	0x36bb,
	0xce4c,	0xdfc5,	0xed5e,	0xfcd7,	0x8868,	0x99e1,	0xab7a,	0xbaf3,
	0x5285,	0x430c,	0x7197,	0x601e,	0x14a1,	0x0528,	0x37b3,	0x263a,
	0xdecd,	0xcf44,	0xfddf,	0xec56,	0x98e9,	0x8960,	0xbbfb,	0xaa72,
	0x6306,	0x728f,	0x4014,	0x519d,	0x2522,	0x34ab,	0x0630,	0x17b9,
	0xef4e,	0xfec7,	0xcc5c,	0xddd5,	0xa96a,	0xb8e3,	0x8a78,	0x9bf1,
	0x7387,	0x620e,	0x5095,	0x411c,	0x35a3,	0x242a,	0x16b1,	0x0738,
	0xffcf,	0xee46,	0xdcdd,	0xcd54,	0xb9eb,	0xa862,	0x9af9,	0x8b70,
	0x8408,	0x9581,	0xa71a,	0xb693,	0xc22c,	0xd3a5,	0xe13e,	0xf0b7,
	0x0840,	0x19c9,	0x2b52,	0x3adb,	0x4e64,	0x5fed,	0x6d76,	0x7cff,
	0x9489,	0x8500,	0xb79b,	0xa612,	0xd2ad,	0xc324,	0xf1bf,	0xe036,
	0x18c1,	0x0948,	0x3bd3,	0x2a5a,	0x5ee5,	0x4f6c,	0x7df7,	0x6c7e,
	0xa50a,	0xb483,	0x8618,	0x9791,	0xe32e,	0xf2a7,	0xc03c,	0xd1b5,
	0x2942,	0x38cb,	0x0a50,	0x1bd9,	0x6f66,	0x7eef,	0x4c74,	0x5dfd,
	0xb58b,	0xa402,	0x9699,	0x8710,	0xf3af,	0xe226,	0xd0bd,	0xc134,
	0x39c3,	0x284a,	0x1ad1,	0x0b58,	0x7fe7,	0x6e6e,	0x5cf5,	0x4d7c,
	0xc60c,	0xd785,	0xe51e,	0xf497,	0x8028,	0x91a1,	0xa33a,	0xb2b3,
	0x4a44,	0x5bcd,	0x6956,	0x78df,	0x0c60,	0x1de9,	0x2f72,	0x3efb,
	0xd68d,	0xc704,	0xf59f,	0xe416,	0x90a9,	0x8120,	0xb3bb,	0xa232,
	0x5ac5,	0x4b4c,	0x79d7,	0x685e,	0x1ce1,	0x0d68,	0x3ff3,	0x2e7a,
	0xe70e,	0xf687,	0xc41c,	0xd595,	0xa12a,	0xb0a3,	0x8238,	0x93b1,
	0x6b46,	0x7acf,	0x4854,	0x59dd,	0x2d62,	0x3ceb,	0x0e70,	0x1ff9,
	0xf78f,	0xe606,	0xd49d,	0xc514,	0xb1ab,	0xa022,	0x92b9,	0x8330,
	0x7bc7,	0x6a4e,	0x58d5,	0x495c,	0x3de3,	0x2c6a,	0x1ef1,	0x0f78
};


struct  ppp_async_info {
	u_int	pai_flags;
#define	PAI_FLAGS_INUSE		0x1
#define	PAI_FLAGS_FLUSH		0x2
#define	PAI_FLAGS_ESCAPED 	0x4
#define	PAI_FLAGS_COMPPROT	0x8
#define	PAI_FLAGS_COMPAC	0x10

	u_long	pai_asyncmap;			/* current outgoing asyncmap */
	int	pai_buffsize;			/* how big of an input buffer to alloc */
	int	pai_buffcount;			/* how many chars currently in the input buffer */
	u_short	pai_fcs;			/* the current fcs */
	mblk_t	*pai_buffer;			/* pointer to the current buffer list */
	mblk_t	*pai_bufftail;			/* pointer to the current input block */
};

typedef	struct ppp_async_info	PAI;

static PAI pai[NPPP];		/* our private cache of async control structures */



static int
ppp_async_open(q, dev, flag, sflag)
	queue_t	*q;
	dev_t	dev;
	int	flag,
		sflag;
/* an open function might fail if we don't have any more pai elements left free 

*/

{
	register PAI	*p;
	register int x;
	int	s;

	if(!suser()) {
		u.u_error = EPERM;
		return(OPENFAIL);	/* only let the superuser or setuid root ppl open
						this module */
	}
	
	if(q->q_ptr) {
		u.u_error = EBUSY;
		return(OPENFAIL);
	}
	s = splstr();
	if(!q->q_ptr) {
		for(x=0; x < NPPP; x++) 	/* search for an empty PAI */
			if(!(pai[x].pai_flags & PAI_FLAGS_INUSE))
				break;
		if(x == NPPP)	{		/* all buffers in use */
			splx(s);		/* restore processor state */
			u.u_error = ENOBUFS;
			return (OPENFAIL);
		}
		p = &pai[x];
		DLOG("ppp_async%d: opening\n",x);
	}
	else {
		p = (PAI *) q->q_ptr;
		DLOG("ppp_async%d: reopen\n", (p-pai)/sizeof(PAI));
	}
	WR(q)->q_ptr = q->q_ptr =  (caddr_t) p;	/* point the write Q and this read Q to private data. */			
	p->pai_flags = PAI_FLAGS_INUSE;
	p->pai_asyncmap = 0xffffffff;		/* default async map */
	p->pai_buffsize = PPP_MTU + sizeof(struct ppp_header) + 
			sizeof(u_short);	/* how big of a buffer to alloc */
	p->pai_buffcount = 0;			/* number of chars currently in buffer */
	p->pai_buffer = NULL;
	splx(s);
	return(0);
}

static int
ppp_async_close(q)
	queue_t	*q;			/* queue info */
{
	int	s;
	register PAI 	*p;

	s = splstr();
	if(p = (PAI *) q->q_ptr) {
		p->pai_flags = 0;	/* clear all flags */
		if(p->pai_buffer) {	/* currently receiving some chars, discard the buffer */
			freemsg(p->pai_buffer);
			p->pai_buffer = NULL;
		}
		DLOG("ppp_async%d: closing\n",(p-pai)/sizeof(PAI));
	}
	splx(s);
	return(0);			
}


static int
ppp_async_wput(q, mp)
	queue_t  *q;
	register mblk_t *mp;

/* M_IOCTL processing is performed at this level. There is some weirdness here, but I couldn't
think of an easier way to handle it.

   SIOCSIFASYNCMAP and SIOCGIFASYNCMAP is handled here.

   SIOCSIFCOMPAC and SIOCSIFCOMPPROT are both handled here, rather than jamming new flag bits
   into the if_ interface.  However the upper ppp_if.c module will set COMPAC and
   COMPPROT flags too, it just doesn't generate the IOACK.

   SIOCSIFMRU and SIOCGIFMRU (Max Receive Unit) are both handled here.
   Rather than using the MTU to set the MRU, we have a seperate IOCTL for it.

*/
{

	register struct iocblk	*i;
	register PAI	*p;
	int	x;

	switch (mp->b_datap->db_type) {

		case 	M_FLUSH :
			if(*mp->b_rptr & FLUSHW)
				flushq(q, FLUSHDATA);
			putnext(q, mp);		/* send it along too */
			break;

		case	M_DATA :
			putq(q, mp);	/* queue it for my service routine */
			break;

		case	M_IOCTL :
			i = (struct iocblk *) mp->b_rptr;
			p =  (PAI *) q->q_ptr;
			switch (i->ioc_cmd) {

				case SIOCSIFCOMPAC :	/* enable or disable AC compression */
					if(i->ioc_count != sizeof(u_char)) {
						i->ioc_error = EINVAL;
						goto iocnak;
					}
					DLOG("ppp_async: SIFCOMPAC %d\n",*(u_char *) mp->b_cont->b_rptr);
					if( *(u_char *) mp->b_cont->b_rptr) 
						p->pai_flags |= PAI_FLAGS_COMPAC;
					else
						p->pai_flags &= ~PAI_FLAGS_COMPAC;
					i->ioc_count = 0;
					goto iocack;
				case SIOCSIFCOMPPROT:	/* enable or disable PROT  compression */
					if(i->ioc_count != sizeof(u_char)) {
						i->ioc_error = EINVAL;
						goto iocnak;
					}

					DLOG("ppp_async: SIFCOMPPROT %d\n",*(u_char *) mp->b_cont->b_rptr);
					if( *(u_char *) mp->b_cont->b_rptr) 
						p->pai_flags |= PAI_FLAGS_COMPPROT;
					else
						p->pai_flags &= ~PAI_FLAGS_COMPPROT;
					i->ioc_count = 0;
					goto iocack;


				case SIOCSIFMRU 	:
					if(i->ioc_count != sizeof(int)) {
						i->ioc_error = EINVAL;
						goto iocnak;
					}
					x = *(int *) mp->b_cont->b_rptr;
					if(x < PPP_MTU)
						x = PPP_MTU;
					x += sizeof(struct ppp_header) + sizeof(u_short);
					if(x > 4096) { /* couldn't allocb something this big */
						i->ioc_error = EINVAL;
						goto iocnak;
					}
					p->pai_buffsize = x;

					i->ioc_count  = 0;
					goto iocack;
				case SIOCGIFMRU :
					if(mp->b_cont = allocb(sizeof(int), BPRI_MED)) {
						*(int *) mp->b_cont->b_wptr = 
							p->pai_buffsize - (sizeof(struct ppp_header) + 
							sizeof(u_short));
						mp->b_cont->b_wptr += i->ioc_count  = sizeof(int);
						goto iocack;
					}
					i->ioc_error = ENOSR;
					goto iocnak;

				case SIOCGIFASYNCMAP :
					if(mp->b_cont = allocb(sizeof(u_long), BPRI_MED)) {
						*(u_long *) mp->b_cont->b_wptr = p->pai_asyncmap;
						mp->b_cont->b_wptr += i->ioc_count = sizeof(u_long);
						goto iocack;
					}
					i->ioc_error = ENOSR;
					goto iocnak;
						
				case SIOCSIFASYNCMAP :
					
					if(i->ioc_count != sizeof(u_long)) {
						i->ioc_error = EINVAL;
						goto iocnak;	/* ugh, goto */
					}
					DLOG("ppp_async: SIFASYNCMAP %lx\n",*(u_long *) mp->b_cont->b_rptr);
	
					p->pai_asyncmap = *(u_long *) mp->b_cont->b_rptr;
					i->ioc_count = 0;
iocack:;
					mp->b_datap->db_type = M_IOCACK;
					qreply(q,mp);
					break;
iocnak:;
					i->ioc_count = 0;
					mp->b_datap->db_type = M_IOCNAK;
					qreply(q, mp);
					break;
				default:		/* unknown IOCTL call */
					putnext(q,mp);	/* pass it along */
			}
			break;		
		default :
			putnext(q,mp);	/* don't know what to do with this, so send it along*/
	}
}

static int
ppp_async_wsrv(q)
	queue_t	*q;

/* this is an incredibly ugly routine. If you see a better way of doing this, feel free
   to improve it. I'm hoping that the buffer management routines are efficient, in terms
   of dup'ing and adjusting message blocks. Hopefully we won't run out of small message
    blocks. Perhaps some counters should be kept to record how efficient this routine
   is, and how much break up of messages is required. 

   I'm not doing anything funny with stuffing PPP_ESCAPES into the actual data buffers.
   If you get a lot of outgoing errors .. you might need to up your
   NBLK4 parameter by quite a bit.... (see param.c)
*/
{
	register u_char	*cp;
	register PAI	*p;
	register u_short	fcs;
	register mblk_t *mp;
	u_char		*start, *stop,c;
	mblk_t	*m1, *m0, *outgoing;

	while((mp = getq(q)) != NULL) {
		/* we can only get M_DATA types into our Queue, due to our Put function */
		if(!canput(q->q_next)) {
			putbq(q, mp);
			return;
		}
		if(msgdsize(mp) < (sizeof(u_char) + sizeof(u_short))) {	/* at least a protocol
									and FCS required */
			freemsg(mp);	/* discard the message */
			putctl1(OTHERQ(q), M_CTL, IF_OUTPUT_ERROR);	/* indicate an output error */
			continue;
		}
		m0 = mp;	/* remember first message block */
		p = (PAI *) q->q_ptr;
		outgoing = NULL;
		fcs = PPP_INITFCS;
#define	SPECIAL(p,c)	((c) == PPP_FLAG) || ((c) == PPP_ESCAPE) ||  \
				((c) < 0x20 && ((p)->pai_asyncmap & (1 << (c))))

		/* for each block in the message, scan between the start and stop
		    markers for escaped chars. dup the message block and chain in
		    the PPP_ESCAPE char and the PPP_TRANS'd character. Continue processing
		    for all of this data block, then for all remaining blocks
		*/
		while(mp) {
			start = mp->b_rptr;
			stop = mp->b_wptr;
			while(start < stop ) 	{
				for(cp = start; cp < stop; cp++) {
					if(SPECIAL(p,*cp))
						break;
					fcs = PPP_FCS(fcs, *cp);
				}
					
				if(cp - start) {
					/* dup the message block, up to len chars */
					m1 = dupb(mp);
					if(!m1)
						goto nobuffs;
					 /* discard chars at front */
					adjmsg(m1, start - mp->b_rptr);

					/* throw away remaining chars */
					adjmsg(m1,  cp - stop);
					if(outgoing)
						linkb(outgoing, m1);
					else
						outgoing = m1;
				} 
				if(cp < stop) {	/* a special char must follow */
					m1 = allocb(2 * sizeof(u_char),BPRI_LO);
					if(!m1)
						goto nobuffs;
					*m1->b_wptr++ = PPP_ESCAPE;
					*m1->b_wptr++ = *cp ^ PPP_TRANS;
					fcs = PPP_FCS(fcs, *cp);
					if(outgoing)
						linkb(outgoing, m1);
					else
						outgoing = m1;
				}
				else
					break;	/* no sense in doing another add and 
						   compare */
				start = cp + 1;
			}  /* end while start < stop */
			mp = mp->b_cont;	/* look at the next block */
		} /* end while(mp) */
		m1 = allocb(sizeof(u_char) + 2 * sizeof(u_short), BPRI_LO);
		if(!m1)
			goto nobuffs;
		fcs ^= 0xffff;			/* XOR the resulting FCS */
		c = fcs & 0xff;
		if(SPECIAL(p,c)) {
			*m1->b_wptr++ = PPP_ESCAPE;
			*m1->b_wptr++ = c ^ PPP_TRANS;
		}
		else
			*m1->b_wptr++ = c;
		c = fcs >> 8;
		if(SPECIAL(p,c)) {
			*m1->b_wptr++ = PPP_ESCAPE;
			*m1->b_wptr++ = c ^ PPP_TRANS;
		}
		else
			*m1->b_wptr++  = c;

		*m1->b_wptr++ = PPP_FLAG;	/* add trailing PPP_FLAG */
		linkb(outgoing, m1);		/* gee, we better have an outgoing by now */
		/* now we check to see if the lower queue has entries, if so, we assume
			that we don't need a leading PPP_FLAG because these packets
			will be sent back to back */
		if(!qsize(q->q_next)) {	/* no entries in next queue, have to add a leading 
					  PPP_FLAG */
			m1 = allocb(sizeof(u_char), BPRI_LO);
			if(!m1)
				goto nobuffs;
			*m1->b_wptr++ = PPP_FLAG;
			linkb(m1, outgoing);
			outgoing = m1;
		}
		/* phew, ready to ship */
		putnext(q, outgoing);
		freemsg(m0);		/* discard original message block pointers */
		continue;
nobuffs:;	/* well, we ran out of memory somewhere */
		if(outgoing)
			freemsg(outgoing);		/* throw away what we have already */
		putbq(q, m0);				/* put back the original message */
		putctl1(OTHERQ(q), M_CTL, IF_OUTPUT_ERROR);
		qenable(q);				/* reschedule ourselves for later */
		return;
	} /* end while(getq()) */
}	/* end function */					

static int
ppp_async_rput(q, mp)
	queue_t *q;
	register mblk_t *mp;
{

	switch (mp->b_datap->db_type) {

		case 	M_FLUSH :
			if(*mp->b_rptr & FLUSHR)
				flushq(q, FLUSHDATA);
			putnext(q, mp);		/* send it along too */
			break;

		case	M_DATA :
			putq(q, mp);	/* queue it for my service routine */
			break;

		default :
			putnext(q,mp);	/* don't know what to do with this, so send it along*/
	}
}

static int
ppp_async_rsrv(q)
	queue_t	*q;
{
	register mblk_t *mp;
	register PAI	*p;
	register u_char	*cp,c;
	mblk_t	*m0;

	p = (PAI *) q->q_ptr;
#define	INPUT_ERROR(q)	putctl1(OTHERQ(q), M_CTL, IF_INPUT_ERROR)
#define	STUFF_CHAR(p,c)	(*(p)->pai_bufftail->b_wptr++ = (c), (p)->pai_buffcount++)
#define	FLUSHEM(q,p)	(INPUT_ERROR(q), (p)->pai_flags |= PAI_FLAGS_FLUSH)

	while((mp = getq(q)) != NULL) {
		/* we can only get M_DATA types into our Queue, due to our Put function */
		if(!canput(q->q_next)) {
			putbq(q, mp);
			return;
		}
		m0 = mp;	/* remember first message block */
		for(; mp != NULL; mp = mp->b_cont) {	/* for each message block */
			cp = mp->b_rptr;
			while(cp < mp->b_wptr) {
				c = *cp++;
				if(c == PPP_FLAG) {
					p->pai_flags &= ~PAI_FLAGS_FLUSH;	/* clear flush indicater */
					
					if(p->pai_buffcount > sizeof(u_short)) { /* discard FCS */
						adjmsg(p->pai_buffer, -sizeof(u_short));
						p->pai_buffcount -= sizeof(u_short);
					}
					if(p->pai_buffcount < sizeof(struct ppp_header)) {
						if(p->pai_buffcount) {
							INPUT_ERROR(q);
							DLOG("ppp_async: short packet\n",0);
						}
						p->pai_buffcount = 0;
						continue;
					}
					if(p->pai_buffer) {
						if(p->pai_fcs == PPP_GOODFCS)
							putnext(q, p->pai_buffer);
						else {
							INPUT_ERROR(q);
							freemsg(p->pai_buffer);
							DLOG("ppp_async: FCS Error\n",0);
						/* we could be discarding this buffer because of data errors
						   in which case we could have kept it, however we might
						   also be discarding it because it was too small. so
						   we throw it away and the next allocation will be the
						   correct size
						*/
						}
						p->pai_buffer = NULL;
						p->pai_buffcount  = 0;
						continue;
					}
				} 	/* c == PPP_FLAG */
				else if (p->pai_flags & PAI_FLAGS_FLUSH)
					continue;			/* skipping chars */
				else if (c == PPP_ESCAPE) {
					p->pai_flags |= PAI_FLAGS_ESCAPED;
					continue;
				}

				if(p->pai_flags & PAI_FLAGS_ESCAPED) {
					p->pai_flags &= ~PAI_FLAGS_ESCAPED; 	/* clear esc flag */
					c ^= PPP_TRANS;		/* xor's are cool */
				}

				/* here we check to see if we have a buffer. If we don't, we assume
				   that this is the first char for the buffer, and we allocb one */

				if(!p->pai_buffer) {
					/* we allocate buffer chains in blocks of ALLOCBSIZE */
			
					if(!(p->pai_buffer = allocb(ALLOCBSIZE, BPRI_MED))) {
						FLUSHEM(q,p);
						continue;
						/* if we don't get a buffer, is there some way
						   to recover and requeue later? rather than flushing
						    the current packet... ? */
					}
					p->pai_bufftail = p->pai_buffer;
				}
				if(!p->pai_buffcount) {
					p->pai_fcs = PPP_INITFCS;
					if(c != PPP_ALLSTATIONS) {
						if(p->pai_flags & PAI_FLAGS_COMPAC) {
							STUFF_CHAR(p,PPP_ALLSTATIONS);
							STUFF_CHAR(p,PPP_UI);
						}
						else {
							DLOG("ppp_async: missed ALLSTATIONS\n",0);
							FLUSHEM(q,p);
							continue;
						}
					}
				} /* end if !p->pai_buffcount */
				if(p->pai_buffcount == 1 && c != PPP_UI) {
					DLOG("ppp_async: missed UI\n",0);
					FLUSHEM(q,p);
					continue;
				}
				if(p->pai_buffcount == 2 && (c & 1) == 1) {
					if(p->pai_flags & PAI_FLAGS_COMPPROT)
						STUFF_CHAR(p, 0);
					else {
						DLOG("ppp_async: bad protocol high byte\n",0);
						FLUSHEM(q,p);
						continue;
					}
				}
				if(p->pai_buffcount == 3 && (c & 1) == 0) {
					DLOG("ppp_async: bad protocol low byte\n",0);
					FLUSHEM(q,p);
					continue;
				}
				if(p->pai_buffcount >= p->pai_buffsize)	{	/* overrun */
					DLOG("ppp_async: too many chars in input buffer %d\n", p->pai_buffcount);
					FLUSHEM(q,p);
					continue;
				}
				/* determine if we have enough space in the buffer */
				if(p->pai_bufftail->b_wptr >= p->pai_bufftail->b_datap->db_lim) {
					if(p->pai_bufftail = allocb(ALLOCBSIZE, BPRI_MED)) 
						linkb(p->pai_buffer, p->pai_bufftail);
					else {
						DLOG("ppp_async: couldn't get buffer for tail\n",0);
						FLUSHEM(q,p);	/* discard all of it */
						continue;
					}
 				}
				STUFF_CHAR(p,c);
				p->pai_fcs = PPP_FCS(p->pai_fcs, c);
			} /* end while cp  < wptr */
		}	/* end for each block */
		/* discard this message now */
		freemsg(m0);
	}	/* end while  getq */

}


#endif

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.