/*
**	Process receiver buffers
*/

#include	"pconfig.h"
#include	"proto.h"
#include	"packets.h"
#include	"pstats.h"


/* extern int	crc(); */
void	Reply(), Control();
int	Retry();

struct Pktstate precvpkt;
/* static */ Pkt_p	Pkp;
/* static */ char *	pbufp;
/* static */ char *	Sbufp;
/* static */ short	dcount;
/* static */ short	Scount;

#define	Pkt	precvpkt.pkt
#define	State	precvpkt.state
#define	Timo	precvpkt.timo
#define	Header	Pkt.header
#define	Dsize	Pkt.dsize
#define	Data	Pkp->data

#define	Ptyp	(Pkt.header & P_PTYPE)
#define	Cntl	(Pkt.header & P_CNTL)
#define	Channel	P_channel(Pkt.header)
#define	Seq	P_seq(Pkt.header)
#define	Nextseq	pconvs[Channel].rseq



void
#ifndef	Blit
precv(bufp, count)
	register char *	bufp;
	register int	count;
{
	int		haveheader = 0;

	while ( count-- )
#else	Blit
precv(c)
	char		c;
#endif	Blit
	{
		switch ( State )
		{
		 case PR_NULL:
#			ifndef	Blit
			Pkp = (Pkt_p)bufp;
			haveheader++;
			Header = *bufp++;
#			else	Blit
			Header = c;
#			endif	Blit
			if ( !Ptyp )
			{
				PSTATS(PS_BADHDR);
				ptracepkt(Pkp, pstats[PS_BADHDR].descp);
				break;
			}
			Timo = Prtimeout;
			if ( !Ptflag )
			{
				Ptflag++;
#				ifndef	Blit
				(void)alarm(Pscanrate);
#				endif
			}
			State = PR_SIZE;
#			ifndef	Blit
			continue;
#			else
			return;
#			endif

		 case PR_SIZE:
#			ifndef	Blit
			Dsize = *bufp++;
#			else
			Dsize = c;
#			endif
			Scount = Dsize;
			if ( Scount > MAXPKTDSIZE )
			{
				PSTATS(PS_BADSIZE);
				ptracepkt((haveheader?Pkp:&Pkt), pstats[PS_BADSIZE].descp);
				break;
			}
			dcount = Scount + EDSIZE;
#			ifndef	Blit
			if ( dcount <= count && haveheader )
			{
				/* Don't move data */
				Sbufp = bufp;
				bufp += dcount;
				count -= dcount;
				goto check;
			}
#			endif
			Pkp = &Pkt;
			Sbufp = (char *)Data;
			pbufp = Sbufp;
			State = PR_DATA;
#			ifndef	Blit
			continue;
#			else
			return;
#			endif

		 case PR_DATA:
#			ifndef	Blit
			*pbufp++ = *bufp++;
#			else
			*pbufp++ = c;
#			endif
			if ( --dcount > 0 )
#				ifndef	Blit
				continue;
#				else
				return;
#				endif

check:
			/** Now at CRC **/

			plogpkt(Pkp, PLOGIN);

			if ( crc((uchar *)Pkp, (int)(Scount+2)) ) /* if bit fields were independent */
			{
				PSTATS(PS_BADCRC);
				ptracepkt(Pkp, pstats[PS_BADCRC].descp);
			}
			else
			{
				if ( Cntl )
					Control();
				else
				{
					Pcdata = (uchar)0;
					if ( Seq == Nextseq )
					{
						if ( (*Prfuncp)(Channel, Sbufp, Scount) )
						{
							PSTATS(PS_BUSY);
							/* Better to let this timeout,
							** as a following sequence will
							** generate a second retransmission
							*/
							Reply(NAK);
						}
						else
						{
							Nextseq = (Nextseq+1) & (SEQMOD-1);	/* NB rseq is a byte, not a bit field, for efficiency */
							PSTATS(PS_RPKTS);
#							ifdef	PSTATISTICS
							pstats[PS_RBYTES].count += Scount;
#							endif
							pconvs[Channel].cdata[Seq] = Pcdata;
							Reply(ACK);
						}
					}
					else
					if ( Retry() )
					{
						PSTATS(PS_RDUP);
						Reply(ACK);
					}
					else
					{
						PSTATS(PS_OUTSEQ);
						Reply(NAK);
						pdumphist(pstats[PS_OUTSEQ].descp);
					}
				}
			}
		}

		Timo = 0;
		State = PR_NULL;
#ifndef	Blit
	}
#endif
}



/*
**	Deal with control packet
*/

void
Control()
{
	register Pch_p	pcp = &pconvs[Channel];
	register Pks_p	psp = pcp->nextpkt;
	register Pbyte *lastseqp = &pseqtable[Seq+SEQMOD];
	register Pbyte *seqp = lastseqp - (NPCBUFS-1);
	register int	hit = 0;
#	ifdef	Blit
	register int	x = spl1();
#	endif

	if ( Scount == 0 )
		goto ack;

	switch ( Data[0] )
	{
	 case ACK:
		/** This and all lesser sequenced packets ok **/
ack:
		do
		{
			if ( *seqp == P_seq(psp->pkt.header) )
			{
				if ( psp->state != PX_WAIT )
				{
#					if	PDEBUG == 1 || PSTATISTICS == 1
					if ( psp->state != PX_OK )
					{
						PSTATS(PS_BADXST);
						pdumphist(pstats[PS_BADXST].descp);
					}
#					endif
				}
				else
				{
					psp->state = PX_OK;
					psp->timo = 0;
					hit++;
				}
				if ( ++psp >= &pcp->pkts[NPCBUFS] )
					psp = pcp->pkts;
			}
		} while
			( ++seqp <= lastseqp );
		if ( hit )
		{
			pcp->nextpkt = psp;
			pcp->freepkts += hit;
#			ifdef	PSTATISTICS
			pstats[PS_XPKTS].count += hit;
			if ( hit > 1 )
				PSTATS(PS_LOSTACK);
#			endif
			break;
		}
#		if	PDEBUG == 1 || PSTATISTICS == 1
		PSTATS(PS_BADACK);
		pdumphist(pstats[PS_BADACK].descp);
#		endif
#		ifdef	Blit
		splx(x);
#		endif
		return;

	 case NAK:
		/** Retransmit this and all lesser sequenced packets **/

		do
		{
			if ( *seqp == P_seq(psp->pkt.header) )
			{
				if ( psp->state != PX_WAIT )
				{
#					if	PSTATISTICS == 1 || PDEBUG == 1
					if ( psp->state != PX_OK )
					{
						PSTATS(PS_BADXST);
						pdumphist(pstats[PS_BADXST].descp);
					}
#					endif
				}
				else
				{
					psp->timo = Pxtimeout;
					(void)Pxfunc(&psp->pkt, psp->size);
					PSTATS(PS_NAKPKT);
					hit++;
					plogpkt(&psp->pkt, PLOGOUT);
				}
				if ( ++psp >= &pcp->pkts[NPCBUFS] )
					psp = pcp->pkts;
			}
		} while
			( ++seqp <= lastseqp );
		if ( !hit )
		{
			PSTATS(PS_BADNAK);
			pdumphist(pstats[PS_BADNAK].descp);
#			ifdef	Blit
			splx(x);
#			endif
			return;
		}
		break;

	 case PCDATA:
		break;

	 default:
		PSTATS(PS_BADCNTL);
		ptracepkt(Pkp, pstats[PS_BADCNTL].descp);
#		ifdef	Blit
		splx(x);
#		endif
		return;
	}
#	ifdef	Blit
	splx(x);
#	endif

	if ( --Scount > 0 )
	{
		(*Prcfuncp)(Channel, Sbufp+1, Scount);
#		ifdef	PSTATISTICS
		pstats[PS_RCBYTES].count += Scount;
#		endif
	}
}



/*
**	Reply to good packet
*/

void
Reply(ctl)
	Pbyte		ctl;
{
	register int	count;

	Pkt.header |= P_CNTL;
	if ( Pcdata )
	{
		PSTATS(PS_XCBYTES);
		count = 2;
		Pkt.data[1] = Pcdata;
		Pkt.data[0] = ctl;
	}
	else
	if ( ctl != ACK )
	{
		count = 1;
		Pkt.data[0] = ctl;
	}
	else
		count = 0;
	Dsize = count;
	count += 2;	/* if bit fields were independent		*/

	(void)crc((Pbyte *)&Pkt, count);
	count += EDSIZE;

	(void)Pxfunc(&Pkt, count);
	plogpkt(&Pkt, PLOGOUT);
}



/*
**	Non trivial sequence number validation:
**	 is this a valid retransmission?
*/

int
Retry()
{
	register Pbyte *lastseqp = &pseqtable[Nextseq+SEQMOD-1];
	register Pbyte *seqp = lastseqp - (NPCBUFS-1);

	do
		if ( *seqp == Seq )
		{
			Pcdata = pconvs[Channel].cdata[Seq];
			return 1;
		}
	while
		( ++seqp <= lastseqp );

	return 0;
}
