From:	CSBVAX::MRGATE!@KL.SRI.COM:LEICHTER@VENUS.YCC.YALE.EDU@SMTP 24-NOV-1987 04:39
To:	EVERHART
Subj:	re: Help wanted tracing DECnet problem

Received: from VENUS (YALE-ENG-VENUS.ARPA.#Internet) by KL.SRI.COM with TCP; Thu 19 Nov 87 10:14:30-PST
Date: Thu, 19 Nov 87 10:19 EST
From: "Jerry Leichter (LEICHTER-JERRY@CS.YALE.EDU)"
 <LEICHTER@VENUS.YCC.YALE.EDU>
Subject: re: Help wanted tracing DECnet problem
To: info-vax@kl.sri.com, MCGUIRE@GRIN1.BITNET
X-VMS-To: IN%"info-vax@kl.sri.com",IN%"MCGUIRE@GRIN1.BITNET"

	I have a DECnet problem between a VAX 8600, running VMS V4.5 and
	DECnet Phase IV routing, and two Sun 3/280s, running Sun DNA (Sun
	DECnet). These systems are connected via Ethernet.  The performance of
	remote login and file transfer between the VAX and a Sun is horrible.
	...
	The symptom:  Data transmission is intermittent, not continuous.
	During a file transfer (or even during a mirror loop), packets fly for
	less than a second, and then there is dead time for about ten seconds.
	This behavior is repeated until the file is transferred (or the
	requested number of loop iterations are completed).  The link does not
	go down.  On the VAX end, a counter called `unrecognized frame
	destination' is incremented regularly, seemingly each time
	transmission is interrupted but I'm not sure of that.

	Plea for help 1:  Any ideas in general about what's happening and how
	we could fix it?

	Plea for help 2:  What is `unrecognized frame destination' telling me?

A packet is identified by a "to" address and a protocol number.  The VMS
Ethernet drivers allow you to specify an (Maddress,protocol) pair to define
the packets you are interested in, where Maddress is a multi-cast address.
The hardware is set to receive packets arriving for any multicast address any
user has started a protocol on.  If the protocol given doesn't correspond
with any protocol anyone is listening for, the packet has an "unrecognized
frame destination".

On an Ethernet that contains TCP/IP hosts, it is normal to see such packets at
a moderate rate because of a design botch in IP:  IP ARP (Address Resolution
Protocol) packets are sent to the Ethernet broadcast address FF-FF-FF-FF-FF-FF
and thus must be received by EVERY connected host.  Most VMS systems not
running TCP/IP will have no-one listening for ARP packets, and thus will log
an "unrecognized frame destination" for every ARP packet.  (I say this is a
botch in the design because it's exactly for applications like this that
multicast addresses are in the Ethernet protocol.  There should be a multicast
address defined as "All ARP processes".)

HOWEVER, if you are indeed receiving a very large number of such events, the
cause of your problem may be simple:  There was a change in the IP multicast
address used for one of the low-level IP operations - the details escape me,
I think it only shows up during booting.  This change occured between two
recent versions of the Sun OS.  The same change leads to an incompatibility
between Sun's and Wollongong's VMS TCP/IP; Wollongong has a patch.  The
result of these incompatibilities are "broadcast storms", in which certain
events cause a chain-reaction of broadcast messages to flood the Ethernet.
Obviously, this has serious effects on throughput!

	Plea for help 3:  Sun provides software that puts the Sun's Ethernet
	interface card into promiscuous mode and logs all packets that fly by.
	We have done this on the Suns to watch packets on the Suns' DELNI.
	We'd like to do the same thing on the VAX's DELNI, comparing output
	from each during a mirror loop.  We attached a spare Sun workstation
	to the VAX's DELNI, but it would not boot, so we want to try to log
	packets from the VAX. Do we need to write a program to do this
	ourselves?  Has anyone already done this?

Enclosed below is a program I wrote to help diagnose related problems here at
Yale.  The program has a built-in database of names and addresses; this stuff
should be read from a data file, but I've been lazy.  To reduce the size,
I've only left one or two examples of each format in place.  Insert data for
your own network.  I've also not included the nmadef.h and xmdef.h files -
they are very long and easy to create locally.  (If all else fails, just
declaring all the values you need as globalvalues should work.)

You need to have PHYS_IO enabled to run this program.
							-- Jerry
------------------------File PMON.C---------------------------------
/*
 * Promiscuous-mode Ethernet monitor
 *
 * Author:  Jerry Leichter (LEICHTER-JERRY@CS.YALE.EDU)
 */
#include	stdio
#include	iodef
#include	libdef
#include	ssdef
#include	"nmadef.h"		/* Network management parameters*/
#include	"xmdef.h"		/* Interface parameters		*/
#include	"C:vms.h"

#define BUFFSIZE	1500
#define RECEIVERS	3
#define RBUFS		5		/* Receive buffers for driver	*/
#define RCVBUFLEN	1500		/* Driver buffer size		*/

typedef UBYTE ETHER_ADDRESS[6];

LONG	devchan;			/* Ethernet channel		*/

typedef struct edata
{	ETHER_ADDRESS destination;
	ETHER_ADDRESS source;
	WORD protocol;
} EDATA;

typedef struct ebuf
{	RQUEUE		link;
	DATE_TIME	dt;
	IOSB		iosb;
	EDATA		edata;
	char		data[BUFFSIZE];
} EBUF;

#define TEST_CONT(x)	do					\
			{	COND TEST_[2];			\
				if_F (TEST_[1] = (x))		\
				{	TEST_[0] = 1;		\
					SYS$PUTMSG(&TEST_[0]);	\
				}				\
			} while (FALSE)

static struct
{	WORD M__1;	/* NMA$C_PCLI_BUS	Buffer size		*/
	LONG M__2;	/* RCVBUFLEN					*/

	WORD M__3;	/* NMA$C_PCLI_BFN	# of buffers		*/
	LONG M__4;	/* RBUFS					*/

	WORD M__5;	/* NMA$C_PCLI_PAD	Padding			*/
	LONG M__6;	/* NMA$C_STATE_OFF				*/

	WORD M__7;	/* NMA$C_PCLI_PTY	Protocol type		*/
	LONG M__8;	/* 0x0660					*/

	WORD M__13;	/* NMA$C_PCLI_PRM	Promiscuous mode	*/
	LONG M__14;	/* NMA$C_STATE_ON				*/

} setparm =
{	NMA$C_PCLI_BUS,		/* Buffer size				*/
	RCVBUFLEN,

	NMA$C_PCLI_BFN,		/* # of buffers				*/
	RBUFS,

	NMA$C_PCLI_PAD,		/* Padding				*/
	NMA$C_STATE_OFF,

	NMA$C_PCLI_PTY,		/* Protocol type			*/
	0x0660,

	NMA$C_PCLI_PRM,		/* Promiscuous mode			*/
	NMA$C_STATE_ON

};

static struct {LONG M__1; LONG M__2;} setparmdsc =
	 { sizeof(setparm), &setparm };

RQUEUE	zrq = {0 , 0};
RQUEUE	*fEBUF;
RQUEUE	*work;

int	buferror;

void		initDefs();
void		receiver();
char		*lookupEA();
char		*lookupPR();
void		setReceiver();
EBUF		*getEBUF();
unsigned int	swap();

BOOLEAN	initdnodes	= TRUE;
BOOLEAN	initonodes	= TRUE;
BOOLEAN	initproto	= TRUE;
BOOLEAN	showtime	= FALSE;

#define freeEBUF(ebuf)	TEST(LIB$INSQTI(ebuf,fEBUF))

main(argc,argv)
int	argc;
char	*argv[];
{	int	i;
	IOSB	iosb;
	EBUF	*ebuf;

	$DESCRIPTOR(devdsc1,"XEA0");	/* Unit to use...		*/
	$DESCRIPTOR(devdsc2,"XQA0");	/*   or maybe this.		*/

	argc = getredirection(argc,argv);

	for (i = 1; i < argc; i++)
	{	if (argv[i][0] == '-')
		{	if (strlen(argv[i]) != 2)
				usage();
			switch (argv[i][1])
			{
		case 'd':
				initdnodes = FALSE;
				break;
		case 'o':
				initonodes = FALSE;
				break;
		case 'p':
				initproto = FALSE;
				break;
		case 't':
				showtime = TRUE;
				break;
		default:
				usage();
			}
		}
		else	usage();
	}
			
	initDefs();
	if (FAILED(SYS$ASSIGN(&devdsc1,&devchan,0,0)))
		TEST(SYS$ASSIGN(&devdsc2,&devchan,0,0));

	TEST(LIB$GET_VM(&sizeof(RQUEUE),&fEBUF));
	*fEBUF = zrq;
	TEST(LIB$GET_VM(&sizeof(RQUEUE),&work));
	*work = zrq;

	TEST(SYS$QIOW(0,		/* EFN				*/
		devchan,		/* Device channel		*/
		IO$_SETMODE|IO$M_CTRL|IO$M_STARTUP,
					/* Function			*/
		&iosb,			/* IOSB				*/
		0,			/* astaddr			*/
		0,			/* astparm			*/
		0,			/* p1				*/
		&setparmdsc,		/* p2				*/
		0,			/* p3				*/
		0,			/* p4				*/
		0,			/* p5				*/
		0			/* p6				*/
		));
	TEST(iosb.status);

	for (i = 0; i < RECEIVERS; i++)
	{	ebuf = getEBUF();
		setReceiver(ebuf);
	}
	for (;;)
	{	if_F (LIB$REMQHI(work,&ebuf))
		{	SYS$HIBER();
		}
		else
		{	if_T (showtime)
			{	static	$DESCRIPTOR(timedesc,"00:00:00.00");
				char	time_string[24];

				SYS$ASCTIM(0,&timedesc,&ebuf->dt,1);
				printf("%.11s %4d bytes %s ==> %s - %s\n",
				    timedesc.dsc$a_pointer,
				    ebuf->iosb.count,
				    lookupEA(&ebuf->edata.source,NULL),
				    lookupEA(&ebuf->edata.destination,NULL),
				    lookupPR(ebuf->edata.protocol,NULL));
			}
			else
			{	printf("%4d bytes %s==>%s - %s\n",
				    ebuf->iosb.count,
				    lookupEA(&ebuf->edata.source,NULL),
				    lookupEA(&ebuf->edata.destination,NULL),
				    lookupPR(ebuf->edata.protocol,NULL));
			}
			freeEBUF(ebuf);
		}
	}
}

void
receiver(ebuf)
EBUF	*ebuf;
{
	setReceiver(getEBUF());
	if_T (showtime)
		SYS$GETTIM(&ebuf->dt);
	TEST_CONT(ebuf->iosb.status);
	if (ebuf->iosb.device_data & XM$M_STS_BUFFAIL)
		buferror++;
	if (LIB$INSQTI(ebuf,work) == LIB$_ONEENTQUE)
		SYS$WAKE(0,0);
}

/*
 * Set up an AST routine to receive a buffer (on devchan, for receiver).
 */
void
setReceiver(ebuf)
EBUF	*ebuf;
{
	TEST(SYS$QIO(0,			/* EFN				*/
		devchan,		/* Device channel		*/
		IO$_READVBLK,		/* Function			*/
		&ebuf->iosb,		/* IOSB				*/
		receiver,		/* astaddr			*/
		ebuf,			/* astparm			*/
		&ebuf->data,		/* p1				*/
		sizeof(ebuf->data),	/* p2				*/
		0,			/* p3				*/
		0,			/* p4				*/
		&ebuf->edata,		/* p5				*/
		0			/* p6				*/
		));
}


EBUF
*getEBUF()
{	EBUF	*ebuf;

	if_F (LIB$REMQHI(fEBUF,&ebuf))
		TEST(LIB$GET_VM(&sizeof(EBUF),&ebuf));
	return(ebuf);
}

typedef ADDR	TRIE[256];
TRIE		trie_base;

char *
lookupEA(ea,setname)
ETHER_ADDRESS	ea;
char		*setname;
{	TRIE	*trie_lev;
	TRIE	*t;
	int	i;
	char	*name;

	trie_lev = &trie_base[0];
	for (i = 0; i < 5; i++)
	{	t = (*trie_lev)[ea[i]];
		if (t == NULL)
			t = (*trie_lev)[ea[i]] = calloc(sizeof(TRIE),1);
		trie_lev = t;
	}
	name = (*trie_lev)[ea[5]];
	if (name == NULL)
	{	if (setname == NULL)
		{	name = malloc(19);
			sprintf(name,"%02X-%02X-%02X-%02X-%02X-%02X ",
				ea[0],ea[1],ea[2],ea[3],ea[4],ea[5]);
		}
		else
		{	name = malloc(strlen(setname) + 1);
			strcpy(name,setname);
		}
		(*trie_lev)[ea[5]] = name;
	}
	return(name);
}

TRIE		prot_base;

char *
lookupPR(pr,setname)
int	pr;
char	*setname;
{	TRIE	*t;
	char	*name;
	int	prlow;
	int	prhigh;

	prhigh = pr & 0xFF;
	prlow = (pr >> 8) & 0xFF;
	t = prot_base[prhigh];
	if (t == NULL)
		t = prot_base[prhigh] = calloc(sizeof(TRIE),1);
	name = (*t)[prlow];
	if (name == NULL)
	{	if (setname == NULL)
		{	name = malloc(19);
			if (pr <= 1500)
				sprintf(name,"IEEE format packet");
			else
				sprintf(name,"Unknown protocol %02X-%02X",
					prhigh,prlow);
		}
		else
		{	name = malloc(strlen(setname) + 1);
			sprintf(name,"%26.26s",setname);
		}
		(*t)[prlow] = name;
	}
	return(name);
}

typedef struct node_def
{	int	area;
	int	number;
	char	*name;
} NODE_DEF;

NODE_DEF node_defs[] =
{	{ 53 , 20 , "JERRY" },
/* Replace with your own node definitions */
	{ 0  ,   0 , NULL }
};

typedef struct ea_def
{	ETHER_ADDRESS	ea;
	char		*name;
} EA_DEF;


EA_DEF ea_defs[] =
/*				Max: 19		"abcdefghijklmnopqrs"	*/
{	{ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, "General broadcast"	},
	{ {0xCF, 0x00, 0x00, 0x00, 0x00, 0x00}, "Loopback assist"	},

	{ {0xAB, 0x00, 0x00, 0x01, 0x00, 0x00}, "DECnet Dump/Load"	},
	{ {0xAB, 0x00, 0x00, 0x02, 0x00, 0x00}, "Remote Console"	},
	{ {0xAB, 0x00, 0x00, 0x03, 0x00, 0x00}, "DECnet Routers"	},
	{ {0xAB, 0x00, 0x00, 0x04, 0x00, 0x00}, "DECnet End Nodes"	},
	{ {0xAB, 0x00, 0x03, 0x00, 0x00, 0x00}, "LAT multicast"		},

/**/	{ {0x09, 0x00, 0x2B, 0x00, 0x00, 0x0F}, "LAT servers (?)"	},
/**/	{ {0x09, 0x00, 0x2B, 0x01, 0x00, 0x01}, "DEC bridges (?)"	},
	{ {0x09, 0x00, 0x2B, 0x02, 0x00, 0x00}, "DECnet L2 Rtrs"	},

	/*
	 * The rest of these are particular to a particular configuration;
	 * replace with your own definitions.
	 */
	{ {0x08, 0x00, 0x2B, 0x02, 0x2A, 0xC2}, "LAT TSSRV1 - CS "	},
	{ {0x08, 0x00, 0x2B, 0x04, 0x3F, 0x2F}, "DEBET, Loading dock"	},

	{ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, NULL			}
};

typedef struct pr_def
{	unsigned int	protocol;
	char		*name;
} PR_DEF;

PR_DEF pr_defs[] =
/*	Max: 26	   "abcdefghijklmnopqrstuvwxyz"		*/
{	{ 0x0200 , "XEROX PUP" },
	{ 0x0201 , "PUP Addr. Trans." },
	{ 0x0600 , "XEROX NS IDP" },
	{ 0x0800 , "DOD IP" },
	{ 0x0801 , "X.75 Internet" },
	{ 0x0802 , "NBS Internet" },
	{ 0x0803 , "ECMA Internet" },
	{ 0x0804 , "Chaosnet" },
	{ 0x0805 , "X.25 Level 3" },
	{ 0x0806 , "ARP" },
	{ 0x0807 , "XNS Compatability" },
	{ 0x081C , "Symbolics Private" },
	{ 0x1001 , "IP_trailer_1_block" },
	{ 0x1002 , "IP_trailer_2_block" },
	{ 0x1003 , "IP_trailer_3_block" },
	{ 0x1004 , "IP_trailer_4_block" },
	{ 0x1005 , "IP_trailer_5_block" },
	{ 0x1006 , "IP_trailer_6_block" },
	{ 0x1007 , "IP_trailer_7_block" },
	{ 0x1008 , "IP_trailer_8_block" },
	{ 0x1009 , "IP_trailer_9_block" },
	{ 0x100A , "IP_trailer_10_block" },
	{ 0x100B , "IP_trailer_11_block" },
	{ 0x100C , "IP_trailer_12_block" },
	{ 0x100D , "IP_trailer_13_block" },
	{ 0x100E , "IP_trailer_14_block" },
	{ 0x100F , "IP_trailer_15_block" },
	{ 0x6000 , "DEC reserved" },
	{ 0x6001 , "Dump/Load assistance (MOP)" },
	{ 0x6002 , "Remote Console (CONN VIA)" },
	{ 0x6003 , "DECnet" },
	{ 0x6004 , "LAT" },
	{ 0x6005 , "DEC diagnostics" },
	{ 0x6006 , "DEC reserved for customers" },
	{ 0x6007 , "Local area VAXcluster" },
	{ 0x6008 , "DEC reserved" },
	{ 0x6009 , "DEC reserved" },
	{ 0x8003 , "Cronus VLN" },
	{ 0x8004 , "Cronus Direct" },
	{ 0x8006 , "Nestar" },
	{ 0x8010 , "Excelan" },
	{ 0x8035 , "Reverse ARP" },
	{ 0x8038 , "DEC DEBET management" },
	{ 0x8080 , "Vitalink bridge management" },
	{ 0x809B , "Kinetics Appletalk" },
	{ 0x9001 , "Bridge bridge management" },
	{ 0x9002 , "Bridge terminal servers" },
	{ 0x9000 , "Loopback" },
	{ 0x0000 , NULL }
};

void
initDefs()
{	NODE_DEF	*ndp;
	EA_DEF		*eap;
	PR_DEF		*prp;
	ETHER_ADDRESS	ea = {0xAA, 0x00, 0x04, 0x00, 0x00, 0x00};
	unsigned int	addr;
	char		name[100];

	if (!(initdnodes || initonodes || initproto))
		return;
	fprintf(stderr,"Initializing translation tables, please wait...");
	fflush(stderr);
	if (initdnodes)
	{	for (ndp = node_defs; ndp->area != 0; ndp++)
		{	addr = 1024 * ndp->area + ndp->number;
			ea[4] = addr & 0xFF;
			ea[5] = (addr >> 8) & 0xFF;
			if (ndp->name == NULL)
				sprintf(name,"DECnet node %02.2d.%03.3d",
					ndp->area,ndp->number);
			else	sprintf(name,"DECnet node %-6.6s",ndp->name);
			lookupEA(ea,name);
		}
	}

	if (initonodes)
	{	for (eap = ea_defs; eap->name != NULL; eap++)
		{	sprintf(name,"%-18.18s",eap->name);
			lookupEA(eap->ea,name);
		}
	}

	if (initproto)
	{	for (prp = pr_defs; prp->name != NULL; prp++)
		{	sprintf(name,"%-26.26s",prp->name);
			lookupPR(swap(prp->protocol),name);
		}
	}

	fprintf(stderr,"\n");
	fflush(stderr);
}

unsigned int
swap(i)
unsigned int	i;
{	return(((i & 0xFF) << 8) | ((i >> 8) & 0xFF));
}

usage()
{
	fprintf(stderr,"Usage:\tpmon [-d] [-o] [-p]\n");
	fprintf(stderr,"\t-d\t Don't initialize DECnet node names\n");
	fprintf(stderr,"\t-o\t Don't initialize other address names\n");
	fprintf(stderr,"\t-p\t Don't initialize protocol names\n");
	fprintf(stderr,"\t-t\t Show time of packet reception\n");
	exit(SS$_ABORT);
}

/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 *
 * Modified, 24-Jan-86 by Jerry Leichter
 *	When creating a new output file, force the maximum record size to
 *	512; otherwise, it ends up as 0 (though the C I/O system won't write
 *	a record longer than 512 bytes anyway) which will cause problems if
 *	the file is later opened for APPEND - if the maximum record size is
 *	0, C will use the length of the longest record written to the file
 *	for its buffer!
 */

#ifdef	vms
#include	<stdio.h>
#include	<errno.h>

int
getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, it is erased
 * from the vector.  getredirection() returns a new argc value.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		argc = getredirection(argc, argv);
 *	}
 */
{
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			j;	/* Output index		*/
	int			file;	/* File_descriptor 	*/

	for (j = i = 1; i < argc; i++) {   /* Do all arguments	*/
	    switch (*(ap = argv[i])) {
	    case '<':			/* <file		*/
		if (freopen(++ap, "r", stdin) == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(errno);	/* Is a fatal error	*/
		}
		break;

	    case '>':			/* >file or >>file	*/
		if (*++ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if
		     * the argument was given as ">filename".
		     * access(name, 2) is TRUE if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) != NULL)
			    break;	/* Exit case statement	*/
			perror(ap);	/* Error, can't append	*/
			exit(errno);	/* After access test	*/
		    }			/* If file accessable	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  create(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var", "mrs=512"))
			== -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(errno);	/* is a fatal error	*/
		}			/* If '>' creation	*/
		break;			/* Exit case test	*/

	    default:
		argv[j++] = ap;		/* Not a redirector	*/
		break;			/* Exit case test	*/
	    }
	}				/* For all arguments	*/
	argv[j] = NULL;			/* Terminate argv[]	*/
	return (j);			/* Return new argc	*/
}
#else
getredirection(argc, argv)
int		argc;
char		*argv[];
/*
 * Dummy routine.
 */
{
	return (argv[0], argc);
}
#endif
---------------------------File VMS.H------------------------------
/*
 * VMS-related definitions.  #include's descrip, ssdef.
 *
 * EDITLEVEL=28
 */
#ifndef VMS_INCLUDED
#define VMS_INCLUDED

#include descrip
#include ssdef

/*
 * Fundamental datatypes
 */
typedef int		INT_32;
typedef short		INT_16;
typedef char		INT_8;
typedef char		*ADDR;
typedef char		BYTE;
typedef short		WORD;
typedef int		LONG;
typedef unsigned char	UBYTE;
typedef unsigned short	UWORD;
typedef unsigned int	ULONG;
typedef BYTE		QUADWORD[8];
typedef BYTE		OCTAWORD[16];

typedef LONG		RADDR;			/* Relative address	*/
typedef struct aqueue				/* Absolute queue	*/
{	struct aqueue	*next;
	struct aqueue	*last;
} AQUEUE;

typedef struct rqueue				/* Relative queue	*/
{	RADDR		next;
	RADDR		last;
} RQUEUE;
/*
 * Next and previous entries in a relative queue - r is an RQUEUE (or any
 * type of memory block that starts off with an RQUEUE.)
 */
#define RQNEXT(r)  ((ADDR)&(r) + ((RQUEUE *)&(r))->next)
#define RQLAST(r)  ((ADDR)&(r) + ((RQUEUE *)&(r))->last)

typedef struct arglist				/* Argument list	*/
{	UBYTE		count;
	BYTE		mbz[3];
	LONG		args[];
} ARGLIST;

typedef struct frame				/* Stack frame		*/
{	ADDR		handler;
	WORD		psw;
	WORD		spa_mask;
	ARGLIST		*ap;
	struct frame	*fp;
	LONG		pc;
	LONG		regs[];
} FRAME;

/*
 * The following are useful for picking individual pieces out of objects
 * up to a quadword in size.
 */
typedef struct longs
{	LONG	l0;
	LONG	l1;
} LONGS;

typedef struct words
{	WORD	w0;
	WORD	w1;
	WORD	w2;
	WORD	w3;
} WORDS;

typedef struct bytes
{	BYTE	b0;
	BYTE	b1;
	BYTE	b2;
	BYTE	b3;
	BYTE	b4;
	BYTE	b5;
	BYTE	b6;
	BYTE	b7;
} BYTES;

/*
 * VMS data structures; refer to the System Services Reference Manual.
 * The names used here are the same as those in that manual except when
 * those are too bulky.
 *
 * This list is not complete; only things that have been useful at one
 * time or another are included.
 */
typedef struct addr_range
{	ADDR start;
	ADDR end;
}			ADDR_RANGE;

typedef void		(*ASTADR)();		/* AST routine address	*/
typedef LONG		BOOLEAN;		/* Truth value		*/
typedef WORD		CHANNEL;		/* Channel number	*/
typedef LONG		COND;			/* Condition value	*/
typedef LONG		CONTEXT;		/* Search context	*/

/*
 * The date and time is a quadword, but defining it as a pair of longs
 * makes it easier to do static initializations of date-times that are
 * short enough - less than 7 minutes is safe - to fit in just the low-
 * order word.  The DELTA_TIME macro creates an appropriate initializer -
 * DELTA_TIME(n) creates a VMS delta time of n hundreds of seconds.  No check
 * is made to ensure that n is in range (less than 42000 is safe).
 *
 * The units for data-times are 100-nanoseconds (1e-7); the input is in
 * hundreds (1e-2); so we must multiply by 1e5, and negate since delta
 * times are represented that way....
 */
typedef struct date_time
{	LONG	low;
	LONG	high;
}			DATE_TIME;		/* VMS date-time	*/
#define DELTA_TIME(n)	{ -100000*(n), -1 }

typedef struct dsc$descriptor_s
			DESCR;			/* (String) descriptor	*/

typedef LONG		EF;			/* Event flag number	*/
typedef BYTE		EFB;			/* Event flag in a byte	*/

typedef LONG		EXH_BLOCK[];		/* exit_handler_block	*/
typedef struct exh_block_i
{	ADDR	EXH_flink;
	ADDR	EXH_addr;
	UBYTE	EXH_argc;
	BYTE	mbz[3];
	COND	*EXH_cond;
	LONG	EXH_args[];
} EXH_BLOCK_I;
#define EXH_FILL(n) 0,0,n+1,0
/*
 * Use as:
 *
 *	EXH_BLOCK exh = {EXH_FILL(n),<n> longword args};
 *
 * where n>=0 is the number of arguments to be passed (not counting the con-
 * dition value),  and cond is someplace a condition value can be passed.
 * EXH_BLOCK_I is for access to the internal structure.  Thus, the next
 * step is likely to be:
 *
 *	exh.EXH_addr = &exh-routine;
 *	exh.EXH_cond = &cond-location;
 */

typedef WORD		FILE_PROT;		/* File protection	*/
typedef struct file_prot_i
{	variant_union
	{   variant_struct
	    {	unsigned system:4;		/* System bits		*/
		unsigned owner:4;		/* Owner bits		*/
		unsigned group:4;		/* Group bits		*/
		unsigned world:4;		/* World bits		*/
	    } wholes;

	    variant_struct
	    { 	unsigned sr:1;			/* System read		*/
		unsigned sw:1;			/* System write		*/
		unsigned se:1;			/* System execute	*/
		unsigned sd:1;			/* System delete	*/

	     	unsigned or:1;			/* Owner read		*/
		unsigned ow:1;			/* Owner write		*/
		unsigned oe:1;			/* Owner execute	*/
		unsigned od:1;			/* Owner delete		*/

	     	unsigned gr:1;			/* Group read		*/
		unsigned gw:1;			/* Group write		*/
		unsigned ge:1;			/* Group execute	*/
		unsigned gd:1;			/* Group delete		*/

	     	unsigned wr:1;			/* World read		*/
		unsigned ww:1;			/* World write		*/
		unsigned we:1;			/* World execute	*/
		unsigned wd:1;			/* World delete		*/
	     } bits;
	} whatever;
} FILE_PROT_I;

typedef struct iosb
{	UWORD status;
	UWORD count;
	LONG device_data;
} IOSB;						/* I/O status block	*/

typedef struct item_list_2
{	WORD len;				/* ?UWORD		*/
	WORD code;
	ADDR addr;
} ITEM_LIST_2;					/* Item list element	*/

typedef struct item_list_3
{	WORD len;				/* ?UWORD		*/
	WORD code;
	ADDR addr;
	ADDR retlen;
} ITEM_LIST_3;					/* Item list element	*/

typedef LONG		PROCESS_ID;		/* Process ID		*/

/*
 * Define the standard BOOLEAN values, and test a VMS-standard condition for
 * success or failure.  Note:  TRUE and FALSE should really be cast to type
 * BOOLEAN, but that would lead to conflicts with stdio.h, which uses just 0
 * and 1.  The main reason the definitions are here at all is to be sure that
 * some incompatible value - say ~0 for TRUE - isn't used.
 */
#define TRUE		1
#define FALSE		0
#define OK(x)		(((x)&1) != 0)
#define FAILED(x)	(((x)&1) == 0)

/*
 * If-statement versions of the VMS-standard truth tests.  For expressions
 * that can only be 0 or 1, we get better code if we just test the bottom bit.
 */
#define if_F(x)		if (FAILED(x))
#define if_T(x)		if (OK(x))

/*
 * Test a VMS-standard status for success.  Any non-success code is signalled.
 * The macro is defined so that it can be used even as a single statement in
 * an if with an else clause.
 */
#define TEST(x) do {	COND TEST_;			\
							\
			if_F ((TEST_ = (x)))		\
				LIB$SIGNAL(TEST_);	\
		   } while (FALSE)

extern DESCR *descr();				/* Create a descriptor	*/

/*
 * Macros to compute a bit value from a bit number, bit number from bit value,
 * and the bit count (number of bits on) for 32-bit quantities.  The results
 * are compile-time constants if the arguments are.
 *
 * The definition of BITCOUNT is based on an algorithm published in an MIT
 * HACKMEM, converted to work on the VAX (sort of) by Rick(?) Merrill, then
 * re-written by Jerry Leichter.  It assumes a 32-bit word.  Note that the
 * argument is evaluated multiple times, so should not have side-effects.
 * A full explanation of the algorithm is too long to include here.  In
 * summary, BX_(x) replaces each 4-bit sequence in x by the number of bits in
 * that sequence (which will, of course, fit in 4 bits).  BITCOUNT folds the
 * 4-bit sequences into 8-bit sequences.  The 8-bit sequences are then added
 * up by recognizing that they can be viewed as a base 256 number, and the sum
 * of the digits of a base b number is the same mod b-1 as the number mod b-1.
 *
 * The original implementation produced results in groups of 3, then 6, bits.
 * While natural on a PDP-10's 36-bit word, this requires special case
 * handling of the sign bit on a VAX, since the top 3-bit sequence is really
 * only 2 bits long. 
 */
#define BITVAL(n)	(1 << (n))
#define BITNUM(b)	BITCOUNT((b)-1)
#define BITCOUNT(x)	(((BX_(x)+(BX_(x)>>4)) & 0x0F0F0F0F) % 255)
#define  BX_(x)		((x) - (((x)>>1)&0x77777777)			\
			     - (((x)>>2)&0x33333333)			\
			     - (((x)>>3)&0x11111111))

#endif
