#include <ascii.h>
#include "fido.h"
#include "fidomem.h"
#include "proto.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.

next_node(packets)
	Chooses the next node to call from the list of .NTC records.
	If there is a node to call left, it loads the structures to
	memory and returns true. 

	This prevents needless disk thrashing by remembering if a
	node was found last call; if not, then it simpyl returns false.

	If 'packets' is non-zero, then it picks a "random" index
	for the .NTC list, to avoid calling nodes in nodelist order,
	and forces choosing the first node.

have_node()
	If a node was found by next_node(), loads the 
	node records, and returns the record number, else -1 if none.


find_poll(n)
	Locate the next record with the POLL bit set, and return its
	record number or -1 if not found. Exists only for performance
	purposes. (10:1!)

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.

make_trav()
	Inits the nodemap traversal index to be best case
non-repeating. Chooses a non-multiple prime number. Must be called
after making packets.






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 */

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

	if (! node_files()) {			/* if files OK */
		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();
		}
	}
/*	if (nodefile == -1) clprintf(0,"!! open_node() error in nodefile\r\n");
	if (idxfile == -1) clprintf(0,"!! open_node() error in idxfile\r\n");
	if (ndxfile == -1) clprintf(0,"!! open_node() error in ndxfile\r\n");
	if (zdxfile == -1) clprintf(0,"!! open_node() error in zdxfile\r\n");
	if (ndatfile == -1) clprintf(0,"!! open_node() error in ndatfile\r\n");
	if (ntcfile == -1) clprintf(0,"!! open_node() error in ntcfile\r\n");
*/
	if (node_files()) 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[512];
int f;
unsigned n;

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

	name= &buff[100];			/* cheap & easy 2nd buffer */
	sprintf(buff,0,"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(SM+200,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(SM+115); /* disk full! */
				close(f);	/* abandon this */
				delete(name);
				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);
	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. */

int 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 */
char 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((struct _node *)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((struct _node *)ip,&target)) break;
				recd= ip-> pos;		/* (cuz NODE search is linear) */
				if (same_node((struct _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((struct _node *)ip,&target)) return(recd);
				if (! same_net((struct _node *)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

*/

void 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 */
	}
}

/* Select the next node to call. If none was found last time, then dont
try to look for another. (Minimized disk thrashing.) The FORCE flag 
is used to start the thing initially. Returns 0 if no more nodes to call. */

int next_node(packets)
unsigned packets;
{
int i;

	if (packets) {
		nodetotal= packets;			/* remember number of packets */
		make_trav(packets);			/* generate the index */
		node_found= 0;				/* force the first pass */
	}
	if (node_found == -1) return(0);		/* none last time */

	for (i= 0; i < nodetotal; i++) {
		nn= (nn + traversal) % nodetotal;
		if (get_ntc(nn) == -1) continue;	/* get the NTC index, */
		if (					/* must be: */
		     (! (ntc.bits & NTC_SUCCESS))	/*   not mailed to yet, */
		  && (! (ntc.bits & NTC_HOLD))		/*   not HOLDing */
		  && (ntc.connects < connect_tries)	/*   under connect limit, */
		  && (ntc.tries < dial_tries)) {	/*   more tries left */
			node_found= nn;			/* yes we have one */
			return(1);			/* stop looking */
		}
	}
	node_found= -1;					/* none to call */
	return(0);
}

/* If a node was found to call, load the records. Returns the node
record number, or -1 if none to call. */

int have_node() {

	while (node_found != -1) {		/* get a valid system */
		get_ntc(node_found);		/* get basic info */
		get_node(ntc.recd);		/* get routing info */
		get_ndat(ntc.recd);		/* get physical data */

		if (ntc.bits & NTC_SUCCESS) {
			next_node(0);		/* was picked up */

		} else if (*ndat.phone == '!') { /* no phone, mark as HOLD */
			clprintf(SM+201,str_node(&ntc.node));
			ntc.bits |= NTC_HOLD;
			put_ntc(node_found);	/* find another */
			next_node(0);		/* try again */

		} else break;			/* found one OK */
	}
	return(node_found);
}

/* Calculate the traversal index such that we go through the table
of nodes non-sequentially. Ideally, this would be random, but
just non-sequential is OK.

	If all else fails, it will use 1. */

int primes[] = {449,353,307,257,227,167,149,131,
	113,101,83,37,23,19,17,13,11,7,5,3,2,1};

void make_trav(n)
unsigned n;
{
int i;

	for (i= 0; primes[i] != 1; i++) {	/* stop when 1 is found */
		if ((primes[i] < n) && (n % primes[i] != 0)) 
			break;			/* or a non-factor */
	}
	nn= 0;					/* initial index, */
	traversal= primes[i];			/* index increment */
}

/* 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[512];
char *next_li();

	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,(struct _node *)&index);	/* set node address, */

	ndat.node.point= 0;				/* clear copied garbage */
	cp= buff;
	ndat.cost= atoi(cp); cp= next_li(cp);		/* set the cost, */
	ndat.rate= atoi(cp); cp= next_li(cp);		/* baud rate, */
	cpynli(ndat.name,cp,24); cp= next_li(cp);	/* copy/process name, */
	cpynli(ndat.phone,cp,40); cp= next_li(cp);	/* phone, */
	cpynli(ndat.city,cp,40); cp= next_li(cp);	/* city */
	cpynli(ndat.pwd,cp,8); cp= next_li(cp);		/* 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 && (*s != ' ') && --n) {
		if (*s == '_') *d++= ' ';
		else *d++= *s;
		++s;
	}
	*d= NUL;
}

/* Skip to the next nodelist.bbs argument. */

static char *next_li(cp)
char *cp;
{
	while (*cp && (*cp != ' ')) ++cp;		/* skip non-blanks, */
	while (*cp == ' ') ++cp;			/* then spaces */
	return(cp);
}

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

void 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. */

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

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

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

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

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

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

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

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

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

int 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. */

int 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. */

int 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. */

int 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 the
structures are treated as char * pointers. */

void cpy_node(d,s)
struct _node *d,*s;
{
int i;
	for (i= sizeof(struct _node); i--;)
		*(char *) d++= *(char *) 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,0,"%d:",n-> zone);
	sprintf(cp,0,(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. */

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

/* Make a full nodelist pathname. */

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