#include <ascii.h>
#include <fido.h>
#include <fidomem.h>

/*

open_node()
	This opens the nodelist files, and leaves the global
handles set. If any one of the files is missing, all handles
are closed:

	NODELIST.BBS		system name, city, phone #
	NODELIST.NMP (see below)node data; route-to, etc
	NODELIST.IDX		index
	NODELIST.NDX		net index/index
	NODELIST.ZDX		zone index/index
	NODELIST.NTC		Nodes To Call

	NTC is an index used at FidoNet time, and is a sequential
	list of nodes & record numbers of nodes that have mail 
	packets; a faster way to locate them.

	open_nmap(event) should be used to open a specific nodemap.

close_node()
	Closes the files above, and mark them closed. (handle == -1)

open_nmap(event)
	This opens the correct nodemap file; if FBIT_KEEPNMP is
	false, then it simply uses NODELIST.NMP. Otherwise, if an event
	is running (event != -1) it looks for NODEMAP.tag. If it doesnt
	exist, it is created by copying NODELIST.NMP.

	(When an event starts, the router will make the current file,
	whatever it is, the proper nodemap for this event (via route
	files, etc. This is preserved in NODELIST.NMP, or NODEMAP.tag,
	if KEEPNMP is on.)

makenname(dest,src)
	Create a full pathname using the node path.

index_node(&node)
	Return the record number of the specified node, or -1 if
not found. This does a logical compare, ie. negative net/node
numbers are normalized to 0 for comparison.

find_node(&node)
	Locate a node in the nodemap, return its record number or -1
if not found. This does a logical compare, as above. 

find_ndat(&node)
	Locate a node in the nodelist, return its record number or -1
if not found. This does a logical compare, as above. The node structure
in memory is filled in.

get_ndat(recd)
	Read the specified ndat record. Returns its index, or -1 if error.
This fills in the ndat structure from the ASCII .BBS file that contains 
the system information, and the node number from the .IDX index file.

ntc_file()
	Creates a new, empty NODELIST.NTC file.

get_ntc(recd)
put_ntc(recd)
	Read or write the specified NTC record. Returns its index,
or -1 if error.

find_ntc(node)
	Search for the specified NTC record, return its record number
and the record loaded. This does an exact zone:net/node.point match.

get_node(recd)
put_node(recd)
	Read or write the specified node record. Returns its index,
or -1 if error.






NODES AND POINTS:

The functions below copy, compare, and otherwise process _node
structures. Comparisons uses only zone, net and number; point is
generally not used, since it's handling is limited to replying, entering
and packeting for points to pickup; there is no routing of points.


fix_node(&node)
	Fixes negative net and node numbers to 0.

is_zonehost(&node)
is_reghost(&node)
is_nethost(&node)
is_host(&node)
	Returns true if the node is a zone, a region or net host, or either.

is_point(&node)
	Returns true if this node is a point.

is_us(&node)
	Returns true if this node is us. (ID or ALTID)

cpy_node(&dest,&src)
	Copies one node into another, including point.

char *str_node(&node)
	Generates a static string expression of the node, and returns a 
pointer to it. Through a kludge it can be called up to 4 times without
overwriting a previous invokation. The point field will be expressed if
it is non-zero.

same_zone(&n1,&n2)	Both are same zone
same_net(&n1,&n2)	Both are same zone and net/region
same_node(&n1,&n2)	Both are same zone, net/region and number
same_point(&n1,&n2)	Both are same zone, net/region, number and point

set_nn(string,&node)

	Parse the string into various zone:net/node numbers. This accepts
"zone:net/node" format. If zone or net isnt specified, it is left untouched.
This will parse points.

*/

static unsigned nn,traversal;		/* node indexing */
static unsigned nodetotal;		/* total packets */
static int node_found;			/* node record number, */

/* Open all the NODELIST files; The indices are not buffered. */

open_node() {
char ln[SS];
struct _rptrcd *r;

	/* the index_node() routine does buffering on these */

	makenname(ln,"nodelist.idx");
	idxfile= open(ln,0 + 0x100);		/* we read this only */
	makenname(ln,"nodelist.ndx");
	ndxfile= open(ln,0 + 0x100);		/* we read this only */
	makenname(ln,"nodelist.zdx");
	zdxfile= open(ln,0 + 0x100);		/* we read this only */

	/* These we let the system buffer */

	makenname(ln,"nodelist.nmp");		/* open basic NMP file */
	nodefile= open(ln,2);
	makenname(ln,"nodelist.bbs");
	ndatfile= open(ln,0);			/* we read this only */
	makenname(ln,"nodelist.ntc");
	ntcfile= open(ln,2);			/* we read/write this */

	if (node_files()) return;		/* missing files */

/* Check the repeat-record for the correct version number. Note that
we read only part of the record. */

	lseek(nodefile,0L,0);
	read(nodefile,ln,sizeof(ln));
	r= (struct _rptrcd *) ln;		/* cast it */
	if (r-> revision != NODEVERS) {
		clprintf(SM+199);		/* wrong revision */
		close_node();
	}
}

/* Close the node file */

close_node() {

	if (nodefile != -1) close(nodefile);
	if (idxfile != -1) close(idxfile);
	if (ndxfile != -1) close(ndxfile);
	if (zdxfile != -1) close(zdxfile);
	if (ndatfile != -1) close(ndatfile);
	if (ntcfile != -1) close(ntcfile);
	nodefile= -1;
	ndatfile= -1;
	idxfile= -1;
	ndxfile= -1;
	zdxfile= -1;
	ntcfile= -1;
}

/* Return true if any one of the nodelist files is not open. */

node_files() {

	return((nodefile == -1) || (idxfile == -1) || (ndxfile == -1) 
	    || (zdxfile == -1) || (ndatfile == -1) || (ntcfile == -1));
}

/* Open the nodemap file. If KEEP_NMP is not enabled, or no event is
running, it simply returns, as NODELIST.NMP was opened by open_node(). 
Otherwise, it tries to open NODEMAP.tag; if that fails, it creates
one by copying NODELIST.NMP. */

open_nmap(e)
int e;
{
char *name,buff[500];
int f;
unsigned n;

	if ((e == -1) || !fbit(FBIT_KEEPNMP)) return;

	name= &buff[100];			/* cheap & easy 2nd buffer */
	sprintf(buff,"NODEMAP.%c",fido.sched[e].tag);
	makenname(name,buff);			/* build the right filename */
	f= open(name,2);			/* open the nodemap file, */
	if (f == -1) {				/* if it doesnt exist, */
		cprintf(" * Creating new nodemap %s\r\n",buff);
		f= creat(name,2);		/* make a new one, */
		if (f == -1) return;		/* oops! */
		lseek(nodefile,0L,0);		/* the entire file! */
		while (n= read(nodefile,buff,sizeof(buff))) {
			if (write(f,buff,n) != n) {
				clprintf("!DISK FULL!\r\n"); /* disk full! */
				close(f);	/* abandon this */
				return;
			}
		}
	}
	close(nodefile);			/* close .NMP */
	nodefile= f;				/* replace with NODEMAP.tag */
}

/* Load the specified nodemap entry. This is built from the .IDX file (node
address portion) and the nodemap data from the .NMP file. */

get_node(recd)
int recd;
{
long o;

	o= 0L + recd; o *= sizeof(struct _idx);
	lseek(idxfile,o,0);				/* node address */
	if (read(idxfile,&nmap.node,sizeof(struct _node)) != sizeof(struct _node))
		return(-1);				/* from .IDX */
	nmap.node.point= 0;				/* (clean up mess) */

	o= 0L + recd; o *= sizeof(struct _nmp);
	o += sizeof(struct _rptrcd);			/* skip the repeat recd */
	lseek(nodefile,o,0);				/* get nmap recd */
	if (read(nodefile,&nmap.route_recd,sizeof(struct _nmp)) != sizeof(struct _nmp))
		return(-1);

	if (nmap.attr & NMAP_REGION) nmap.node.number= -1; /* flag REGION hosts */
	return(recd);
}

/* Find the next record with the POLL bit set, starting at the specified
record numbers. Returns the record number found, or -1 if not found. */

find_poll(recd)
unsigned recd;
{
struct _nmp *np,buff[100];	/* for file buffering */
unsigned ii,ic;
long o;

	o= 0L + recd; o *= sizeof(struct _nmp);		/* starting position */
	o += sizeof(struct _rptrcd);			/* skip the repeat recd */
	lseek(nodefile,o,0);				/* seek there */
	ii= ic= 0;					/* buffer empty, etc */

	while (1) {
		if (ii >= ic) {				/* load buffer */
			ii= 0;
			ic= read(nodefile,buff,sizeof(buff)) / sizeof(struct _nmp);
			if (! ic) return(-1);		/* not found -- no such node */
			np= (struct _nmp *) buff;
		}
		if (np-> bits & NMAP_POLL) {		/* if a POLL recd */
			get_node(recd);			/* load the record */
			return(recd);			/* found one */
		}
		++recd; ++np; ++ii;
	}
}

/* Write out a node record; return -1 if error. Of course only the nodemap
data is written out. */

put_node(recd)
int recd;
{
long o;

	o= 0L + recd; o *= sizeof(struct _nmp);
	o += sizeof(struct _rptrcd);			/* skip the repeat recd */
	lseek(nodefile,o,0);
	write(nodefile,&nmap.route_recd,sizeof(struct _nmp));
	return(recd);
}


/* Create a new NODELIST.NTC file; close the existing one, and create
a new one. Returns 0 if something goes wrong. */

ntc_file() {
char buff[SS];

	close(ntcfile);					/* close current, */
	makenname(buff,"NODELIST.NTC");
	ntcfile= creat(buff,2);				/* create a new one */
	return(!node_files());				/* 0 == error */
}

/* Load a node index record into memory; return the record number. */

get_ntc(recd)
int recd;
{
long o;

	o= 0L + recd;
	o *= sizeof(struct _ntc);
	lseek(ntcfile,o,0);
	if (read(ntcfile,&ntc,sizeof(struct _ntc)) != sizeof(struct _ntc)) recd= -1;
	return(recd);
}

/* Write out a ntc record; return -1 if error. */

put_ntc(recd)
int recd;
{
long o;

	o= 0L + recd;
	o *= sizeof(struct _ntc);
	lseek(ntcfile,o,0);
	write(ntcfile,&ntc,sizeof(struct _ntc));
	return(recd);
}

/* Search for an NTC record by node; this is just a brute force linear 
search; not much else we can do, besides there are almost always very
few of them. */

find_ntc(n)
struct _node *n;
{
int r;

	r= 0;
	while (get_ntc(r) != -1) {	/* hard search */
		if (same_point(n,&ntc.node)) return(r);
		++r;
	}
	return(-1);			/* not found */
}

/* Return the record number of this node, or -1 if not found. This works by 
searching through the three indices: the ZONE INDEX to locate the first node
in that zone; the net/region index to find the first record in that net; then
a sequential search to locate the specific node. This works with the nodelist 
in any order. 

Note that there is not a _node structure in the index file, but we treat it
as one; the elements (zone, net, number) are in the correct order, and point
is not used by the comparison functions; cpy_node will copy two bytes of what
follows the number, but we just fill that part in later. 

We use the same code for all three searches because the files are so similar.
The NDX and ZDX files contain record numbers in the position field; the IDX
file does not, and so we have to set the starting recd number from the NDX
file and increment as we search IDX.

Also there are some speedups: in the NDX file, we check for the correct
node itself; default host routing should increase the probability of hits.
(The ZDX record number is relative to the NDX file itself; the NDX record
number is relative to the IDX file and the rest of the database.) Also in 
the IDX search, since the list is heirarchical by definition, if we encounter 
another NET during the search, we can assume that the target node does not 
exist. */

index_node(n)
struct _node *n;
{
struct _node target;		/* logical node to search for */
int recd;			/* record number, from ZDX and NDX file */
struct _idx *ip,buff[100];	/* for file buffering */
unsigned ii,ic;
int f;				/* file we are currently searching */
int level;			/* file rotator */

	cpy_node(&target,n);				/* make a copy, */
	fix_node(&target);				/* normalize for compare */

	f= zdxfile;					/* start with ZONE index */
	lseek(zdxfile,0L,0);				/* seek to BOF */
	level= ii= ic= 0;				/* buffer empty, etc */

	while (1) {
		if (ii >= ic) {				/* load buffer */
			ii= 0;
			ic= (read(f,buff,sizeof(buff)) / sizeof(struct _idx));
			if (! ic) return(-1);		/* not found -- no such node */
			ip= (struct _idx *) buff;
		}
		switch (level) {
			case 0:				/* ZONE index */
				if (! same_zone(ip,&target)) break;
				recd= ip-> pos;		/* (cuz NODE search is linear) */
				f= ndxfile;		/* found ZONE host */
				goto start;		/* continue from next file */

			case 1:				/* NET index */
				if (! same_net(ip,&target)) break;
				recd= ip-> pos;		/* (cuz NODE search is linear) */
				if (same_node(ip,&target)) return(recd);
				f= idxfile;		/* found NET host */

start:				++level;		/* next search ... */
				ip-> pos *= sizeof(struct _idx);
				lseek(f,ip-> pos,0);	/* (long) compiler bug */
				ii= ic= 0;		/* buffer empty, etc */
				break;

			case 2:				/* NODE index */
				if (same_node(ip,&target)) return(recd);
				if (! same_net(ip,&target)) return(-1);
				++recd;			/* generate recd number */
				break;
		}
		++ip; ++ii;				/* next struct, next record */
	}
}

/* Find a particular node in the nodemap; return its index 
or -1 if not found. */

find_node(n)
struct _node *n;
{
int recd;

	recd= index_node(n);		/* look for it, */
	if (recd == -1) return(-1);	/* error! */
	recd= get_node(recd);		/* get it if found, */
	return(recd);
}

/* Parse a string into net and node number. zone and net are untouched if
not found in the input string. This parses as follows: (xx means unchanged)

string			output

99:88/77		99:88/77.0
88/77			xx:88/88.0
77			xx:xx/77.0
99:88			99:88/00.0
88/			xx:88/00.0
99:88/77.3		99:88/77.3

*/

set_nn(cp,n)
char *cp;
struct _node *n;
{
int x;

	if (isdigit(*cp)) {
		n-> number= atoi(cp);		/* assume its node only */
		while (isdigit(*cp)) ++cp;	/* skip the number, */
	}
	if (*cp == ':') {			/* if it was a zone, */
		n-> zone= n-> number;		/* that was a zone number, */
		n-> net= atoi(++cp);		/* net must follow */
		while (isdigit(*cp)) ++cp;	/* skip the net number */
		if (*cp == '/') {		/* if that was a net number */
			n-> number= atoi(++cp);	/* set node number (or zero) */
			while (isdigit(*cp)) ++cp; /* skip node number */
		}

	} else if (*cp == '/') {		/* if that was a net number, */
		n-> net= n-> number;		/* copy it up, */
		n-> number= atoi(++cp);		/* now set node number */
		while (isdigit(*cp)) ++cp;	/* skip node number */
	}
	if (*cp == '.') {			/* if a dot */
		n-> point= atoi(++cp);		/* parse point */
	}
}

/* Pull the descriptive info on a particular node from NODELIST.BBS. Return -1
if not found. */

find_ndat(n)
struct _node *n;
{
int recd;

	recd= index_node(n);		/* look for it, */
	if (recd == -1) return(-1);	/* error! */
	recd= get_ndat(recd);		/* get it if found, */
	return(recd);
}

/* Load a ndat record into memory; return the record number. This involves
loading the .IDX index record to get the node address and .BBS file offset
to find the actual data. 

Note that this treats the address within the index file as a _node struct;
since the elements are in the right order this works (except that it copies
2 bytes of garbage instead of the 'point' field). */

get_ndat(recd)
int recd;
{
long o;
struct _idx index;
char *cp,buff[256];

	o= 0L + recd; o *= sizeof(struct _idx);		/* compiler (long) bug */

	lseek(idxfile,o,0);				/* load the .IDX */
	read(idxfile,&index,sizeof(struct _idx));	/* record, */

	lseek(ndatfile,index.pos,0);			/* seek in .BBS file */
	rline(ndatfile,buff,sizeof(buff));		/* load raw data */

	cpy_node(&ndat.node,&index);			/* set node address, */
	ndat.node.point= 0;				/* clear copied garbage */
	cp= buff;
	ndat.cost= atoi(cp); cp= next_arg(cp);		/* set the cost, */
	ndat.rate= atoi(cp); cp= next_arg(cp);		/* baud rate, */
	cpynli(ndat.name,cp,24); cp= next_arg(cp);	/* copy/process name, */
	cpynli(ndat.phone,cp,40); cp= next_arg(cp);	/* phone, */
	cpynli(ndat.city,cp,40); cp= next_arg(cp);	/* city */
	cpynli(ndat.pwd,cp,8); 				/* password */

	return(recd);
}

/* Local routine to copy a nodelist.bbs field item, expanding _'s 
to spaces and stopping at the first delimiter. */

static cpynli(d,s,n)
char *d,*s;
{
	while (*s && !delim(*s) && --n) {
		if (*s == '_') *d++= ' ';
		else *d++= *s;
		++s;
	}
	*d= NUL;
}

/* Set negative node numbers to zero, their logical equivelant, 
for routing, etc. */

fix_node(n)
struct _node *n;
{
	if (n-> net < 0) n-> net= 0;
	if (n-> number < 0) n-> number= 0;
}

/* Return true if this node is a net host. */

is_nethost(n)
struct _node *n;
{
	return(! n-> number);
}

/* Return true if this node is a non-host net. */

is_reghost(n)
struct _node *n;
{
	return(n-> number < 0);
}
/* Return true if this node is a net or region host. */

is_host(n)
struct _node *n;
{
	return(n-> number <= 0);
}

/* Return true if this node is a zone host. */

is_zonehost(n)
struct _node *n;
{
	return((n-> number <= 0) && (n-> net <= 0));
}

/* Return true if this is a point. */

is_point(n)
struct _node *n;
{
	return(n-> point);
}

/* Return true if the two nodes are members of the same net. */

same_zone(n1,n2)
struct _node *n1,*n2;
{
	return(n1-> zone == n2-> zone);
}

/* Return true if the two nodes are members of the same net. */

same_net(n1,n2)
struct _node *n1,*n2;
{
	return(
		(n1-> net == n2-> net) &&
		(n1-> zone == n2-> zone)
	);
}

/* Return true if the two nodes are the same. */

same_node(n1,n2)
struct _node *n1,*n2;
{
	return(
		(n1-> number == n2-> number) &&
		(n1-> net == n2-> net) &&
		(n1-> zone == n2-> zone)
	);
}
/* Return true if the two nodes are the point. */

same_point(n1,n2)
struct _node *n1,*n2;
{
	return(
		(n1-> point == n2-> point) &&
		(n1-> number == n2-> number) &&
		(n1-> net == n2-> net) &&
		(n1-> zone == n2-> zone)
	);
}

/* Copy one node structure into another. IMPORTANT NOTE: Notice that they
structures are treated as char * pointers. */

cpy_node(d,s)
char *d,*s;			/* our code */
/* struct _node *d,*s;		REALITY */
{
int i;
	for (i= sizeof(struct _node); i--;)
		*d++= *s++;
}

/* Return a pointer to a string of the zone:net/node. */

char *str_node(n)
struct _node *n;
{
#define KLUDGE 4			/* strings we can make at once */
static char *cp,work[KLUDGE][40];	/* where we keep them */
static int k;				/* which one as are on */

	k = ++k % KLUDGE;	/* select next ... */
	cp= work[k];		/* easier faster shorter */
	*cp= NUL;		/* empty it */

	sprintf(cp,"%d:",n-> zone);
	sprintf(cp,(n-> point ? "%d:%d/%d.%d" : "%d:%d/%d"),
	    n-> zone,n-> net,n-> number,n-> point);
	return(cp);
}
/* Return true if this net/node combination is us. */

is_us(n)
struct _node *n;
{
	return(same_node(n,&id) || same_node(n,&altid));
}

/* Make a full nodelist pathname. */

makenname(pathname,filename)
char *pathname,*filename;
{
	strcpy(pathname,fido.nodepath);
	strcat(pathname,filename);
}
