{ -*-Fundamental-*- }
{ Phil Budne @ DEC / LCG }
{ Rutgers-PASCAL program to crawl out over a DECnet and map it !! }

PROGRAM crawl(input,output);

CONST
    HASHSIZE = 101;
    NODESIZE = 6;
    EMPTYNODE = '      ';
    CIRCSIZE = 10;
    EMPTYCIRC = '          ';
    DATASIZE = 4000;
    SHIFT = 5;
    DUMPINTERVAL = 25;

TYPE
    nodeindex = 1 .. NODESIZE;
    nodename = PACKED ARRAY [ nodeindex ] OF char;
    nodenum = integer;

    refnode = ^node;
    refcircuit = ^circuit;
    refnlist = ^nlist;

    node =
	PACKED RECORD
	 vnext : refnode;		{ next in visit chain }
	 hnext : refnode;		{ next in hash chain }
	 name : nodename;
	 visited : boolean;		{ visit attempted }
	 failed : boolean;		{ connect failed }
	 none : boolean;		{ no circuits found }
	 clist : refcircuit;
	END;

    circindex = 1 .. CIRCSIZE;
    circname = PACKED ARRAY [ circindex ] OF char;

    circuit =
	RECORD
	 next : refcircuit;
	 id : circname;
	 nodelist : refnlist;
	 owner : refnode
	END;

    nlist =
	RECORD
	 next : refnlist;
	 nptr : refnode
	END;

    byte = 0 .. 255;
    bindex = 1 .. DATASIZE;
    bdata = PACKED ARRAY [ bindex ] OF byte;

    hashindex = 1 .. HASHSIZE;

VAR
    hashtab : ARRAY [ hashindex ] OF refnode;
    visitlist : refnode;
    knowncount, visitcount : integer;

{ state vars for nextcircuit }
    domult : boolean;
    bufcnt : integer;
    bufpnt : bindex;

{****************************************************************}

{ ROUTINES FROM CREEP.MAC }

{ external procedure to return local node name }
PROCEDURE getlocal( VAR name : nodename );
EXTERN;


{ external procedure to open a data connection to a node }
FUNCTION opencon( name : nodename ) : boolean;
EXTERN;


{ external procedure to send a data record on connection }
FUNCTION putdata( cnt : integer; data : bdata ) : boolean;
EXTERN;


{ external procedure to get a data record from connection }
FUNCTION getdata( max : integer; VAR data : bdata ) : integer;
EXTERN;


{ external procedure to close data connection }
PROCEDURE closecon;
EXTERN;
{****************************************************************}

FUNCTION hash(name : nodename) : hashindex;
VAR
    hval : integer;
    i : nodeindex;

BEGIN
    hval := 0;
    FOR i := 1 TO NODESIZE DO
	hval := hval * SHIFT + ord(name[i]);

    hash := (hval MOD HASHSIZE) + 1
END;

{****************************************************************}

FUNCTION newnode(name : nodename) : refnode;
VAR
    np, hp : refnode;
    hval : hashindex;

BEGIN {newnode}
    new(np);
    np^.vnext := visitlist;
    np^.name := name;
    np^.clist := nil;
    np^.visited := false;
    visitlist := np;
    knowncount := knowncount + 1;

    hval := hash( name );
    np^.hnext := hashtab[hval];
    hashtab[hval] := np;
    newnode := np
END; {newnode}
{****************************************************************}

FUNCTION findnode( name : nodename ) : refnode;
VAR
    hval : hashindex;
    tp, np : refnode;
    
BEGIN
    hval := hash( name );

    tp := hashtab[ hval ];
    np := nil;
    WHILE (tp <> nil) AND (np = nil) DO BEGIN
	IF tp^.name = name THEN
	    np := tp;
	tp := tp^.hnext
    END; {while}

    IF np = nil THEN
	np := newnode( name );

    findnode := np

END; {findnode}
{****************************************************************}

FUNCTION showcircuits : boolean;
VAR
    arr : bdata;
BEGIN
    arr[1] := 20;		{ show function }
    arr[2] := 3;		{ dynamic, circuits }
    arr[3] := 255;		{ active }
    arr[4] := 0;
    showcircuits := putdata( 4, arr )
END;
{****************************************************************}

{ This routine reads NICE messages from the show circuits command.
  it should be a co-routine.  It uses the much dreaded GOTO.
  Label 999 is a RETURN statement.
  Label 111 enters the next iteration of the main loop (CONTINUE).

  The following are state variables that should be OWN/STATIC,
  but must belong to the global context;

	domult : boolean;
	bufcnt : integer;
	bufpnt : bindex;
}

FUNCTION nextcircuit( VAR cname : circname; VAR nname : nodename ) : boolean;
VAR
    buf : bdata;
    i, j : integer;
    loop1, loop2 : boolean;
    state, substate, nodenum : integer;

BEGIN
    nextcircuit := false;
    loop1 := true;

    WHILE loop1 DO BEGIN

	cname := EMPTYCIRC;
	nname := EMPTYNODE;

	IF bufcnt = 0 THEN BEGIN

	    bufcnt := getdata( DATASIZE, buf );	{ try to fetch a record }
	    bufpnt := 1;

	    IF bufcnt = 0 THEN			{ failed to refill }
		GOTO 999;

	    IF domult AND (buf[1] = 128) THEN BEGIN
		domult := false;		{ end of mult }
		bufcnt := 0;
		GOTO 999
	    END;

	    IF buf[1] > 128 THEN		{ negative return is an err }
		GOTO 999;

	    IF buf[1] = 2 THEN BEGIN		{ start of mult }
		domult := true;			 { set flag}
		bufcnt := 0;			 { force refil }
		goto 111			 { start all over }
	    END;

	    IF buf[1] <> 1 THEN			{ mult data? }
		GOTO 999;			 { boy are we dumb! }

	    bufcnt := bufcnt - 4;		{ eat resp, and err bytes }
	    bufpnt := bufpnt + 4

	END; {if bufcnt = 0}

	IF bufcnt < 1 THEN			{ expecting entity id }
	    GOTO 999;

	i := buf[bufpnt];			{ get entity leng. }
	bufpnt := bufpnt + 1;
	bufcnt := bufcnt - 1;

	FOR j := 1 TO i DO BEGIN		{ copy entity id }
	    IF bufcnt <= 0 THEN
		GOTO 999;			{ *RAN OUT* }
	    IF j <= CIRCSIZE THEN
		cname[j] := chr( buf[bufpnt] MOD 128 );
	    bufpnt := bufpnt + 1
	END;

	bufcnt := bufcnt - i;

	state := -1;
	substate := -1;
	nodenum := -1;
	loop2 := true;

	WHILE loop2 DO BEGIN			{ loop for parameters }
	    if bufcnt = 0 THEN BEGIN
		loop1 := false;
		GOTO 111
	    END
	    ELSE IF bufcnt < 2 THEN
		GOTO 999;

	    i := buf[bufpnt+1] * 256 + buf[bufpnt];
	    bufpnt := bufpnt + 2;
	    bufcnt := bufcnt - 2;

	    IF i = 0 THEN BEGIN
		IF bufcnt < 2 THEN GOTO 999;
		IF buf[bufpnt] <> 129 THEN GOTO 999; { 0201 coded single}
		state := buf[bufpnt+1];
{		writeln('state: ',state);	}
		bufpnt := bufpnt + 2;
		bufcnt := bufcnt - 2
	    END
	    ELSE IF i = 1 THEN BEGIN
		IF bufcnt < 2 THEN GOTO 999;
		IF buf[bufpnt] <> 129 THEN GOTO 999; { 0201 coded single }
		substate := buf[bufpnt+1];
{		writeln('substate: ',substate);	}
		bufpnt := bufpnt + 2;
		bufcnt := bufcnt - 2
	    END
	    ELSE IF i = 800 THEN BEGIN
		IF bufcnt < 4 THEN GOTO 999;
		IF buf[bufpnt] <> 194 THEN GOTO 999; { 0302 coded mult, 2 rec }

		IF buf[bufpnt+1] <> 2 THEN GOTO 999; { uns dec, len 2 }
		bufpnt := bufpnt + 2;
		nodenum := buf[bufpnt+1] * 256 + buf[bufpnt];
{		writeln('nodenum: ',nodenum);	}
		bufcnt := bufcnt - 4;
		bufpnt := bufpnt + 2;

		IF bufcnt < 1 THEN GOTO 999;
		IF buf[bufpnt] <> 64 THEN GOTO 999; { 0100 = coded ascii }
		bufcnt := bufcnt - 1;
		bufpnt := bufpnt + 1;

		IF bufcnt < 1 THEN GOTO 999;
		i := buf[bufpnt];
		bufcnt := bufcnt - 1;
		IF (bufcnt < i) OR (i > NODESIZE) THEN GOTO 999;
		FOR j := 1 to I DO
		    nname[j] := chr( buf[bufpnt+j] MOD 128 );
{		writeln('nname: ',nname);	}
		bufpnt := bufpnt + i;
		bufcnt := bufcnt - i
	    END
	    ELSE BEGIN
		writeln('?Unknown parameter type ',i);
		GOTO 999
	    END;

{	    writeln('bufcnt: ',bufcnt);	}
	    IF bufcnt = 0 THEN loop2 := false;
	END; {while}

	IF (state <> 0) AND (state <> -1) THEN
	    GOTO 111				{ not on }
	ELSE IF substate <> -1 THEN
	    GOTO 111				{ funny substate set ? }
	ELSE BEGIN
	    nextcircuit := true;
	    bufcnt := 0;
	    GOTO 999
	END;

111:
    END; {while loop1}

999:
END; {nextcircuit}

{****************************************************************}

FUNCTION findcircuit( exec : refnode; cname : circname ) : refcircuit;
VAR
    cp, it : refcircuit;

BEGIN
    cp := exec^.clist;
    it := nil;
    WHILE (cp <> nil) AND (it = nil) DO BEGIN
	IF cname = cp^.id THEN
	    it := cp
	ELSE
	    cp := cp^.next
    END; {while}

    IF it = nil THEN BEGIN		{ no such circuit found? }
	new(it);			{ create new circuit block }
	it^.id := cname;		{ set its id }
	it^.nodelist := nil;		{ no node list yet }
	it^.owner := exec;		{ set owner }
	it^.next := exec^.clist;	{ link into executor's list }
	exec^.clist := it		{ of circuits }
    END;

    findcircuit := it
END; {findcircuit}

{****************************************************************}

FUNCTION docircuits( exec : refnode) : integer;
VAR
    cname : circname;
    nname : nodename;
    cp : refcircuit;
    np : refnode;
    lp : refnlist;
    count : integer;

BEGIN
    count := 0;
    WHILE nextcircuit( cname, nname ) DO BEGIN
	IF (cname <> EMPTYCIRC) AND (nname <> EMPTYNODE) THEN BEGIN
	    count := count + 1;
	    writeln('  ',cname,' to ',nname);
	    cp := findcircuit( exec, cname );
	    new(lp);
            lp^.next := cp^.nodelist;
	    lp^.nptr := findnode( nname );
            cp^.nodelist := lp
	END {if}
    END; {while}
    docircuits := count;
END; {docircuits}

{****************************************************************}

PROCEDURE visit( np : refnode );
VAR
    i : integer;
    darr : bdata;
    dptr : bindex;
    done : boolean;

BEGIN
    visitcount := visitcount + 1;

    writeln(np^.name,'... ');

    IF opencon(np^.name) THEN BEGIN
	domult := false;
	bufcnt := 0;
	writeln(' connected');
	np^.failed := false;
	IF showcircuits THEN
	    IF docircuits(np) = 0 THEN
		np^.none := true
	    ELSE
		np^.none := false;
	closecon
    END {if opencon}
    ELSE BEGIN
	writeln(' failed');
	np^.failed := true
    END;

    np^.visited := true

END; {visit}

{****************************************************************}
PROCEDURE dumptable;
VAR
    index : hashindex;
    np : refnode;
    cp : refcircuit;
    lp : refnlist;
    dumpfile : text;

BEGIN
    rewrite(dumpfile,'CRAWL.DMP');
    FOR index := 1 to HASHSIZE DO BEGIN
	np := hashtab[ index ];
	WHILE np <> nil DO BEGIN
	    cp := np^.clist;
	    IF cp = nil THEN
		IF np^.visited THEN
		    IF np^.failed THEN
			writeln(dumpfile,np^.name,' (FAILED)')
		    ELSE IF np^.none THEN
			writeln(dumpfile,np^.name,' (NONE)')
		    ELSE
			writeln(dumpfile,np^.name,' (VISITED?)')
		ELSE
		    writeln(dumpfile,np^.name,' (UNVISTED)');
	    WHILE cp <> nil DO BEGIN
		lp := cp^.nodelist;
		IF lp = nil THEN
		    writeln(dumpfile,np^.name,'/',cp^.id);
		WHILE lp <> nil DO BEGIN
		    writeln(dumpfile,np^.name,'/',cp^.id,'/',lp^.nptr^.name);
		    lp := lp^.next
		END; {while lp}
		cp := cp^.next
	    END; {while cp}
	    np := np^.hnext
	END {while np}
    END; {for}
    close(dumpfile)
END; {dumptable}

{****************************************************************}

PROCEDURE doit;
VAR
    local, start : nodename;
    current : refnode;
    timer : integer;

BEGIN {doit}
    getlocal(local);
    WRITE('Starting node [',local,'] :');
    READLN(start);
    IF start = EMPTYNODE THEN
	start := local;

    visitlist := newnode(start);
    timer := DUMPINTERVAL;

    WHILE visitlist <> nil DO BEGIN
	writeln('known:',knowncount:5,' visited:',visitcount:5);
	IF timer = 0 THEN BEGIN
	    timer := DUMPINTERVAL;
	    dumptable
	END
	ELSE
	    timer := timer - 1;

	current := visitlist;
	visitlist := current^.vnext;
	IF NOT current^.visited THEN
	    visit(current)
    END {while}

END; {doit}

BEGIN {crawl}
    doit;
    dumptable
END.
