/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:packet.c 12.0$ */
/* $ACIS:packet.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/rvd/server/RCS/packet.c,v $ */

#ifndef lint
static char *rcsid = "$Header:packet.c 12.0$";
#endif


#ifndef lint
static char rcsid_packet_c[] = "$Header:packet.c 12.0$";
#endif lint

/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in the file "notice.h" */
#include "notice.h"

/* This file contains all the general-purpose routines for dealing with
 * RVD packets off the network.  Other routines are allowed to know the
 * packet formats.  Routines in this file deal with allocating and
 * freeing packet buffers, reading and writing packets on the net,
 * checking packets for validity, and so forth.
 */

#include	<stdio.h>
#include	<errno.h>
#include	<syslog.h>
#include	<sys/types.h>
#include	<sys/param.h>
#include	<netinet/in.h>
#include	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/rvd.h>

#include	"rvd_types.h"
#include	"rvdadd.h"
#include	"custom.h"
#include	"obj.h"
#include	"queue.h"
#include	"packet.h"
#include	"extern.h"

#ifdef ibm032
#define swaph(n) ((((n)>>8)&0xff) | (((n)&0xff) << 8))
#define swapw(n) (swaph((n)>>16) | (swaph((n)) << 16))
#else
#define swaph(n) n
#define swapw(n) n
#endif

/* There is a statically-allocated pool of packet buffers in the array buffers.
 * Initially all are linked into the queue headed by freelist; buffers are
 * normally allocated from the freelist and returned there when freed.
 * If more buffers are needed on the fly, buffers are allocated from the heap;
 * such buffers are returned to the heap when freed.
 * To try to minimize paging, the free list is normally managed LIFO.
 * For debugging purposes it's possible to manage the list FIFO - compile
 * this file with the symbol FIFO defined.
 */

static	struct	pkt_q	freelist;	/* list of free packet buffers */
static	struct	rvd_pkt	buffers[NBUFS];	/* initial buffer pool */

#ifdef	FIFO				/* manage first-in, first-out */
#define	get_next()	rem_q_tail(&freelist, struct rvd_pkt *)
#else
#define	get_next()	rem_q_head(&freelist, struct rvd_pkt *)
#endif

/* The pkt_stats structure contains some packet pool statistics */

struct	pkt_stats {
	int	ps_shorts;		/* short packets received */
	int	ps_cksum;		/* bad checksums received */
	int	ps_type;		/* bad type packets rcvd */
	int	ps_version;		/* bad version number */
	int	ps_allocs;		/* number allocated */
	int	ps_frees;		/* number freed */
	int	ps_bbfree;		/* frees of packets inside big buf */
	int	ps_ovflo;		/* number of times buf pool empty */
	int	ps_depth;		/* current number of bufs in use */
	int	ps_max;			/* max number of bufs ever in use */
} pkt_stats;


pkt_init()

/* Initialize the free packet queue and allocate the default initial
 * number of packet buffers.
 */
{
	register struct	rvd_pkt	*pkt;	/* temp pointer */

	q_init(&freelist);		/* init packet free list */
	for (pkt = &buffers[0]; pkt < &buffers[NBUFS]; pkt++) {
		q_init(pkt);		/* fix the links */
		pkt->rp_type = RVD_PKT_TYPE; /* set the type */
		ins_q_tail(pkt, &freelist); /* free it */
	}
}


struct	rvd_pkt *
pkt_alloc()

/* Allocate a packet buffer from the free list, if possible, and return it.
 * If there is no packet buffer in the free list, allocate one from the heap.
 * No guarantees are made about the contents of the packet except that the
 * link and type fields are set up properly.
 */
{
	register struct	rvd_pkt	*pkt;	/* packet to be returned */

	pkt_stats.ps_allocs++;
	if (++(pkt_stats.ps_depth) > pkt_stats.ps_max)
		pkt_stats.ps_max = pkt_stats.ps_depth;
	if ((pkt = get_next()) == NULL) {
		pkt_stats.ps_ovflo++;
		pkt = (struct rvd_pkt *)obj_alloc(sizeof(struct rvd_pkt),
		    RVD_PKT_TYPE);
	}
	return(pkt);
}


pkt_q_free(queue)

/* Free all packets on the specified queue.
 */

register struct pkt_q	*queue;		/* queue of packets to free */
{
	register struct rvd_pkt	*pkt;	/* temp pointer */

	for (pkt = rem_q_head(queue, struct rvd_pkt *); pkt != NULL;
	     pkt = rem_q_head(queue, struct rvd_pkt *) )
		pkt_free(pkt);
}


pkt_free(pkt)

/* Free the specified packet.  If it's in the buffers array, just chain it
 * into the free list (at the head).  If it's inside the bigbuf array
 * (where block packets are constructed in place), just return.  Otherwise,
 * free it.
 */

register struct	rvd_pkt	*pkt;		/* packet to be freed */
{
	if (pkt >= &buffers[0] && pkt < &buffers[NBUFS]) { /* in array? */
		pkt_stats.ps_frees++;
		pkt_stats.ps_depth--;
		ins_q_head(pkt, &freelist);
		return;
	} else if ((char *)pkt >= &bigbuf[0] && (char *)pkt <
	    &bigbuf[(MAXCONTIG * RVDDSIZE) + BLOCKOFF] ) {
		pkt_stats.ps_bbfree++;
		return;
	}
	pkt_stats.ps_frees++;
	pkt_stats.ps_depth--;
	obj_free((char *)pkt);
}


unsigned
pkt_check(pkt)

/* Verify that the specified input packet is valid.  This involves:
 *	verifying the checksum
 *	insuring that the RVD version in the packet is correct
 *	insuring that the packet length is correct for the packet type
 * As part of the packet checks, the contents of the short and long
 * fields in the packet are byte-swapped where necessary.  On entry,
 * the rp_len field is assumed to contain the total received number
 * of bytes in the packet.  On exit, it is modified to contain
 * the length of the RVD header plus the actual data in the packet.
 * Returns RVDNOER if the packet checks out ok, or an RVD error code
 * otherwise.
 */

register struct	rvd_pkt	*pkt;		/* input packet */
{
	register int	len;		/* length of input in bytes */
	register unsigned type;		/* packet type */
	register u_long	cksum;		/* for holding checksum */

#ifdef KERBEROS
	struct rvdas *asup;		/* authenticated spinup pointer. */
	int	 auth_spinsize;		/* authenticated spinup size. */
#endif KERBEROS

	len = pkt->rp_len;
	if (len < sizeof(struct ip)) {
		pkt_stats.ps_shorts++;
		return(RVDEPKT);
	}

/* Use the ip length, as there may be extra local-net padding.  It
 * must be an integral number of longwords.
 */

	len = pkt->rp_ip.ip_len; /* - (pkt->rp_ip.ip_hl * NBPW); */
	if (len < sizeof (struct rvdhdr) || (len % NBPW) != 0) {
		pkt_stats.ps_shorts++;
		return(RVDEPKT);
	}
	pkt->rp_len = len;		/* save the length */

	cksum = pkt->rp_rvd.cksum;	/* check the checksum */
#ifndef	RVD_USER
	cksum = ntohl(cksum);		/* byte-swap as needed */
#endif	RVD_USER

	pkt->rp_rvd.cksum = 0;
	if (cksum != rvd_cksum((long *)&pkt->rp_rvd, len)) {
		syslog(LOG_INFO, " cksum=%x, rvd_cksum=%x", cksum, 
			rvd_cksum((long *)&pkt->rp_rvd, len));
		pkt_stats.ps_cksum++;
		return(RVDECKSM);
	}

	if (pkt->rp_rvd.hdrun.allpkt.version != RVDVERSION) { /* bad version */
		pkt_stats.ps_version++;
		return(RVDEBVER);
	}

	pkt->rp_rvd.drive = swapw(pkt->rp_rvd.drive);	/* swap drive */
	pkt->rp_rvd.nonce = swapw(pkt->rp_rvd.nonce);	/* swap nonce */
	pkt->rp_rvd.index = swapw(pkt->rp_rvd.index);	/* swap index */

	type = pkt->rp_rvd.hdrun.allpkt.rvd_type;
	switch (type) {			/* check lengths, byte-swap */
case RVDRESPIN:				/* respinup packet */
		if (len < sizeof(struct rvdrs))
			goto too_short;	/* sigh, goto's.... */
		pkt->rp_rvd.res_un.vd_uid = swapw(pkt->rp_rvd.res_un.vd_uid);
		break;

case RVDSPIN:				/* spinup packet */
		if (len < sizeof(struct rvds))
			goto too_short;	/* sigh, goto's.... */
		break;			/* nothing to byte-swap here */
		
#ifdef KERBEROS
case RVDAUTHSPIN:			/* authenticated spinup packet */
		asup = (struct rvdas *)&pkt->rp_rvd;
		auth_spinsize = RVDAS_HDR_SIZE + asup->authent.length;
		if (len < auth_spinsize)
			goto too_short;	/* sigh, goto's.... */
		break;			/* nothing to byte-swap here */
#endif KERBEROS
		
case RVDSDOWN:
		if (len < sizeof(struct rvdsd))
			goto too_short;
		break;			/* nothing to byte-swap here */
		
case RVDREAD:
		if (len < sizeof(struct rvdr))
			goto too_short;
		{			/* local variable space */
			register struct	rvdr	*prvd;

			prvd = (struct rvdr *)(&pkt->rp_rvd);
			prvd->blockn = ntohl(prvd->blockn);
			prvd->blockc = ntohl(prvd->blockc);
		}
		break;
		
case RVDWRITE:
		if (len < sizeof(struct rvdw) ||
		   (len - sizeof(struct rvdw)) % RVDDSIZE != 0)
			goto too_short;
		{			/* local variable space */
			register struct	rvdw	*prvd;

			prvd = (struct rvdw *)(&pkt->rp_rvd);
			prvd->blockn = ntohl(prvd->blockn);
			prvd->blockc = ntohs(prvd->blockc);
			prvd->bindex = ntohs(prvd->bindex);
		}
		break;
		
case RVDSPACK:
		if (len < sizeof(struct rvdsa))
			goto too_short;
		{			/* local variable space */
			register struct	rvdsa	*prvd;

			prvd = (struct rvdsa *)(&pkt->rp_rvd);
			prvd->nblocks = ntohl(prvd->nblocks);
			prvd->burst = ntohs(prvd->burst);
			prvd->qlen = ntohs(prvd->qlen);
		}
		break;
		
case RVDERROR:
		if (len < sizeof(struct rvde))
			goto too_short;
		break;			/* nothing to byte-swap here */
		
case RVDACK:
		if (len < sizeof(struct rvdack))
			goto too_short;
		break;			/* nothing to byte-swap */
		
case RVDBLOCK:
		if (len < sizeof(struct rvdb) + RVDDSIZE ||
		   (len - sizeof(struct rvdb)) % RVDDSIZE != 0)
			goto too_short;
		{			/* local variable space */
			register struct	rvdb	*prvd;

			prvd = (struct rvdb *)(&pkt->rp_rvd);
			prvd->blockn = ntohl(prvd->blockn);
			prvd->status = ntohl(prvd->status);
		}
		break;
		
case RVDWACK:
		if (len < sizeof(struct rvdwa))
			goto too_short;
		{			/* local variable space */
			register struct	rvdwa	*prvd;

			prvd = (struct rvdwa *)(&pkt->rp_rvd);
			prvd->blockn = ntohl(prvd->blockn);
			prvd->status = ntohl(prvd->status);
			prvd->bcount = ntohl(prvd->bcount);
		}
		break;
		
default:
		pkt_stats.ps_type++;
		return(RVDEPKT);
too_short:
		pkt_stats.ps_shorts++;
		return(RVDEPKT);

	}
	return(RVDENOER);
}


pkt_complete(pkt)

/* Complete the specified packet to be transmitted.
 * This involves byte-swapping the necessary fields and computing the
 * checksum.  All other fields are assumed to have already been filled in.
 */

register struct	rvd_pkt	*pkt;		/* packet to be sent */
{
	register unsigned	type;	/* packet type */
	register u_long		cksum;	/* packet checksum */
	register int		len;	/* rvd packet length */

	type = pkt->rp_rvd.hdrun.allpkt.rvd_type;

	switch (type) {			/* byte-swap */
case RVDSPIN:				/* spinup packet */
case RVDRESPIN:				/* respinup packet */
case RVDSDOWN:
case RVDACK:
#ifdef KERBEROS
case RVDAUTHSPIN:			/* authenticated spinup packet */
#endif KERBEROS
		break;			/* nothing to byte-swap here */
		
case RVDREAD:
		{			/* local variable space */
			register struct	rvdr	*prvd;

			prvd = (struct rvdr *)(&pkt->rp_rvd);
			prvd->blockn = htonl(prvd->blockn);
			prvd->blockc = htonl(prvd->blockc);
		}
		break;
		
case RVDWRITE:
		{			/* local variable space */
			register struct	rvdw	*prvd;

			prvd = (struct rvdw *)(&pkt->rp_rvd);
			prvd->blockn = htonl(prvd->blockn);
			prvd->blockc = htons(prvd->blockc);
			prvd->bindex = htons(prvd->bindex);
		}
		break;
		
case RVDERROR:
		if (((struct rvde *)(&pkt->rp_rvd))->etype != RVDEIDA)
			break;
			
/* else fall through... god is this a crock... */

case RVDSPACK:
		{			/* local variable space */
			register struct	rvdsa	*prvd;

			prvd = (struct rvdsa *)(&pkt->rp_rvd);
			prvd->nblocks = htonl(prvd->nblocks);
			prvd->burst = htons(prvd->burst);
			prvd->qlen = htons(prvd->qlen);
		}
		break;
		
case RVDBLOCK:
		{			/* local variable space */
			register struct	rvdb	*prvd;

			prvd = (struct rvdb *)(&pkt->rp_rvd);
			prvd->blockn = htonl(prvd->blockn);
			prvd->status = htonl(prvd->status);
		}
		break;
		
case RVDWACK:
		{			/* local variable space */
			register struct	rvdwa	*prvd;

			prvd = (struct rvdwa *)(&pkt->rp_rvd);
			prvd->blockn = htonl(prvd->blockn);
			prvd->status = htonl(prvd->status);
			prvd->bcount = htonl(prvd->bcount);
		}
		break;
		
default:
		bughalt("pkt_complete: bad packet type");

	}

	len = pkt->rp_len;		/* pick up rvd packet length */
	pkt->rp_rvd.drive = swapw(pkt->rp_rvd.drive);	/* swap drive */
	pkt->rp_rvd.nonce = swapw(pkt->rp_rvd.nonce);	/* swap nonce */
	pkt->rp_rvd.index = swapw(pkt->rp_rvd.index);	/* swap index */
	pkt->rp_rvd.cksum = 0;
	cksum = rvd_cksum((long *)&pkt->rp_rvd, len);
	cksum = ntohl(cksum);		/* byte-swap as needed */
	pkt->rp_rvd.cksum = cksum;
}


pkt_show()

/* Show packet statistics in log.
 */
{
	syslog(LOG_INFO, "Packet pool statistics:");
	syslog(LOG_INFO, " %8d    short packets", pkt_stats.ps_shorts);
	syslog(LOG_INFO, " %8d    checksum errors", pkt_stats.ps_cksum);
	syslog(LOG_INFO, " %8d    bad types", pkt_stats.ps_type);
	syslog(LOG_INFO, " %8d    bad versions", pkt_stats.ps_version);
	syslog(LOG_INFO, " %8d    allocates", pkt_stats.ps_allocs);
	syslog(LOG_INFO, " %8d    frees", pkt_stats.ps_frees);
	syslog(LOG_INFO, " %8d    big buf frees", pkt_stats.ps_bbfree);
	syslog(LOG_INFO, " %8d    pool overflows", pkt_stats.ps_ovflo);
	syslog(LOG_INFO, " %8d    currently in use", pkt_stats.ps_depth);
	syslog(LOG_INFO, " %8d    maximum in use", pkt_stats.ps_max);
}
