/*
Copyright 2001-2022 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

/*
Copyright 2001-2022 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

//
//	C replacement for L3Code.asm
//
#define Kernel

#define _CRT_SECURE_NO_DEPRECATE 

#pragma data_seg("_BPQDATA")

#include "time.h"
#include "stdio.h"
#include <fcntl.h>

#include "cheaders.h"
#include "tncinfo.h"

VOID UPDATEDESTLIST();
VOID MOVEALL(dest_list * DEST);
VOID MOVE3TO2(dest_list * DEST);
VOID CLEARTHIRD(dest_list * DEST);
VOID L3TRYNEXTDEST(struct ROUTE * ROUTE);
VOID SendNETROMRoute(struct PORTCONTROL * PORT, unsigned char * axcall);
void SendVARANetromNodes(struct TNCINFO * TNC, MESSAGE *Buffer);
void SendVARANetromMsg(struct TNCINFO * TNC,L3MESSAGEBUFFER * Buffer);
VOID SENDNODESMSG(int Portnum);
VOID TCPNETROMSend(struct ROUTE * Route, struct _L3MESSAGEBUFFER * Frame);

extern int NODESINPROGRESS;
extern int NODESToOnePort;
extern int CLOSING;

extern BOOL NODESINPROGRESS ;;
PPORTCONTROL L3CURRENTPORT;
extern dest_list * CURRENTNODE;

int L3_10SECS = 10;

extern int PREFERINP3ROUTES;


VOID L3BG()
{
	//	TRANSFER MESSAGES FROM DEST TO LINK

	int n = MAXDESTS;
	struct DEST_LIST * DEST = DESTS;		// NODE LIST
	struct PORTCONTROL * PORT = PORTTABLE;
	struct ROUTE * ROUTE;
	struct TNCINFO * TNC;

	struct _LINKTABLE * LINK;

	while (n--)
	{
		if (DEST->DEST_CALL[0])				// Active entry?
		{
			while(DEST->DEST_Q)				// FRAMES TO SEND?
			{
				int ActiveRoute = DEST->DEST_ROUTE;

				if (ActiveRoute)
				{
					ROUTE = DEST->NRROUTE[ActiveRoute - 1].ROUT_NEIGHBOUR;

					// if NetROM over VARA or NetROM over TCP pass direct to the driver

					if (ROUTE && ROUTE->TCPPort)
					{
						PL3MESSAGEBUFFER Frame = (PL3MESSAGEBUFFER)Q_REM(&DEST->DEST_Q);
						TCPNETROMSend(ROUTE, Frame);
						ReleaseBuffer(Frame);
						continue;
					}

					if (ROUTE)
					{
						TNC = TNCInfo[ROUTE->NEIGHBOUR_PORT];

						if (TNC && TNC->NetRomMode)
						{
							PL3MESSAGEBUFFER MSG = (PL3MESSAGEBUFFER)Q_REM(&DEST->DEST_Q);
							SendVARANetromMsg(TNC, MSG);
							return;
						}
					}
					if (ROUTE)
						LINK = ROUTE->NEIGHBOUR_LINK;
					else
						LINK= NULL;

					if (LINK)
					{
						if (LINK->L2STATE == 0)
						{
							//	LINK ENTRY IS INVALID - IT PROBABLY HAS BEEN 'ZAPPED', SO CANCEL IT
							
							ROUTE->NEIGHBOUR_LINK = NULL;
						}
						else
						{
							if (LINK->L2STATE < 5)
							{
								goto NextDest;			// Wait for it to activate
							}
							else
							{
								ROUTE->NBOUR_IFRAMES++;
								C_Q_ADD(&LINK->TX_Q, Q_REM(&DEST->DEST_Q));
								continue;				// See if more
							}
						}
					}
					// Drop through to Activate
				}
				
				// No Active Route

				if (ACTIVATE_DEST(DEST) == FALSE)
				{
					// Node has no routes - get rid of it

					REMOVENODE(DEST);
					return;					// Avoid risk of looking at old entries
				}
			}
		}
		
NextDest:
		DEST++;
	}
}

BOOL ACTIVATE_DEST(struct DEST_LIST * DEST)
{
	int n = MAXDESTS;
	struct PORTCONTROL * PORT = PORTTABLE;
	struct ROUTE * ROUTE = NULL;
	struct _LINKTABLE * LINK;
	struct TNCINFO * TNC;

	int ActiveRoute;

	if (DEST->DEST_ROUTE == 0)		// Don't ALREADY HAVE A SELECTED ROUTE?
	{
		DEST->DEST_ROUTE = 1;

		if (PREFERINP3ROUTES)
		{
			// if we have any INP3 routes use the first. It will always be the fastest. The others are there for fallback if the first fails.

			if (ROUTE = DEST->INP3ROUTE[0].ROUT_NEIGHBOUR)
				DEST->DEST_ROUTE = 4;		// TRY TO ACTIVATE FIRST
		}
	}
		
	ActiveRoute = DEST->DEST_ROUTE - 1;

	ROUTE = DEST->NRROUTE[ActiveRoute].ROUT_NEIGHBOUR;

	if (ROUTE == 0)
	{
		//	Current Route not present
		//	If  current route is 1, then we must have INP3 routes (or entry is corrupt)

		if (DEST->DEST_ROUTE != 1)
			goto NOROUTETODEST;

		// Current Route is 1

		if (DEST->INP3ROUTE[0].ROUT_NEIGHBOUR == 0)
			return FALSE;					// No INP3 so No Routes

		DEST->DEST_ROUTE = 4;			// First INP3
		ROUTE = DEST->INP3ROUTE[0].ROUT_NEIGHBOUR;
	}

	if (ROUTE == 0)			// Shouldn't happen
		return FALSE;


	// if NetROM over VARA conection is made by the driver

	TNC = TNCInfo[ROUTE->NEIGHBOUR_PORT];

	if (TNC && TNC->NetRomMode)
	{
		return TRUE;
	}

	LINK = ROUTE->NEIGHBOUR_LINK; 
	
	if (LINK == 0)
	{
		// Need to Activate Link
	
		// SET UP LINK TABLE ENTRY

		return L2SETUPCROSSLINK(ROUTE);
	}
	
	// We must be waiting for link to come up
	
	return TRUE;

NOROUTETODEST:

	//	CURRENT NEIGHBOUR NOT DEFINED - RESET TO USE FIRST

	if (DEST->DEST_ROUTE == 1)
		return FALSE;				// First not defined  so give up
	
	DEST->DEST_ROUTE = 0;
	return TRUE;
}

char Call1[10];
char Call2[10];
char Call3[10];

VOID PROCESSNODESPOLL(struct PORTCONTROL * PORT)
{
	// Pauls G8BPT's request NODES Broadcast

	SENDNODESMSG(PORT->PORTNUMBER);
	return;
}
VOID PROCESSNODEMESSAGE(MESSAGE * Msg, struct PORTCONTROL * PORT)
{
	//	PROCESS A NET/ROM 'NODES' MESSAGE

	//	    UPDATE _NEIGHBOURS LIST WITH ORIGINATING CALL, AND
	//	    DESTINATION LIST WITH ANY PRACTICAL ROUTES


	struct DEST_LIST * DEST;
	struct ROUTE * ROUTE;
	int Portno = PORT->PORTNUMBER;
	time_t Stamp;
	int HH, MM;
	int Msglen = Msg->LENGTH;
	int n;
	UCHAR * ptr1, * ptr2, * saveptr;
	int Qual;
	APPLCALLS * APPL;
	int App;
	

	if (PORT->PORTQUALITY == 0 || PORT->INP3ONLY)
		return;

	//	SEE IF OUR CALL - DONT WANT TO PUT IT IN LIST!

	if (CompareCalls(Msg->ORIGIN, NETROMCALL))
		return;

	if (CheckExcludeList(Msg->ORIGIN) == 0)
		return;
	
	for (App = 0; App < NumberofAppls; App++)
	{
		APPL=&APPLCALLTABLE[App];

		if (CompareCalls(Msg->ORIGIN, APPL->APPLCALL))
			return;
	}

	Msg->ORIGIN[6] &= 0x1E;			// MASK OFF LAST ADDR BIT

	// Trap Empty Call

	if (Msg->ORIGIN[0] == 0x40)
		return;

/*
	// validate call	ptr = &Buffer->ORIGIN[0];
	n = 6;

	while(n--)
	{
		// Try a bit harder to detect corruption

		c = *(ptr++);

		if (c & 1)
		{
			ReleaseBuffer(Buffer);
			return;
		}

		c = c >> 1;
		
		if (!isalnum(c) && !(c == '#') && !(c == ' '))
		{
			ReleaseBuffer(Buffer);
			return;
		}
	}

*/
	//	SEE IF ORIGINATING CALL IS IN NEIGHBOUR LIST - IF NOT ADD IT

	if (FindNeighbour(Msg->ORIGIN, Portno, &ROUTE) == 0)
	{
		// Not in list

		if (ROUTE == NULL)
			return;					// Table full

		//	CREATE NEIGHBOUR RECORD

		memcpy(ROUTE->NEIGHBOUR_CALL, Msg->ORIGIN, 7);

		ROUTE->NEIGHBOUR_PORT = Portno;
		ROUTE->NEIGHBOUR_QUAL = PORT->PORTQUALITY;
		
		ROUTE->NEIGHBOUR_LINK = 0;		// CANT HAVE A LINK IF NEW _NODE
	
		ROUTE->NoKeepAlive = PORT->PortNoKeepAlive;
	}

	// if locked route with quality zero ignore

	if ((ROUTE->NEIGHBOUR_FLAG))	 // LOCKED ROUTE
		if (ROUTE->NEIGHBOUR_QUAL == 0)
			return;

	//	If Ignoreunlocked set, ignore it not locked

	if ((ROUTE->NEIGHBOUR_FLAG) == 0)	 // LOCKED ROUTE
		if (PORT->IgnoreUnlocked)
			return;
	
	SendNETROMRoute(PORT, Msg->ORIGIN);

	// if not locked, update route quality from port quality (may have changed config and not cleared SAVENODES

	if (ROUTE->NEIGHBOUR_FLAG == 0)	 // Not LOCKED ROUTE
		ROUTE->NEIGHBOUR_QUAL = PORT->PORTQUALITY;

	//	GET TIME FROM BIOS DATA AREA OR RTC

	time((time_t *)&Stamp);

	Stamp = Stamp % 86400;			// Secs into day
	HH = (int)(Stamp / 3600);

	Stamp -= HH * 3600;
	MM = (int)(Stamp / 60);

	ROUTE->NEIGHBOUR_TIME = 256 * HH + MM;

	//	GET QUALITY

	Qual = ROUTEQUAL = ROUTE->NEIGHBOUR_QUAL;		// FOR INITIAL ROUTE TABLE UPDATE
	
	//	CHECK LINK IS IN DEST LIST

	if (FindDestination(Msg->ORIGIN, &DEST) == 0)
	{
		if (DEST == NULL)
			return;				// Tsble Full

		//	CREATE DESTINATION RECORD

		memset(DEST, 0, sizeof(struct DEST_LIST));

		memcpy(DEST->DEST_CALL, Msg->ORIGIN, 7);
	
		NUMBEROFNODES++;
	}

	//	ALWAYS UPDATE ALIAS IN CASE NOT PRESENT IN ORIGINAL TABLE 

	ptr1 = &Msg->L2DATA[1];
	ptr2 = &DEST->DEST_ALIAS[0];

	if (*ptr1  > ' ')			// Only of present
	{
		// Validate Alias, mainly for DISABL KPC3 Problem
		UCHAR c;

		n = 6;
		while (n--)
		{
			c = *(ptr1++);
			if (c < 0x20 || c > 0x7A)
				c = ' ';

			*(ptr2++) = c;
		}
	}
	else
		ptr1 += 6;


	//	UPDATE QUALITY AND OBS COUNT

	PROCROUTES(DEST, ROUTE, Qual);

	Msglen -= (MSGHDDRLEN + 23);				// LEVEL 2 HEADER FLAG and NODE MNEMONIC

	//	PROCESS DESTINATIONS from message

	// ptr1 = start

	saveptr = ptr1 - 21;

	while (Msglen >= 21)		// STILL AT LEAST 1 ENTRY LEFT
	{
		Msglen -= 21;
		saveptr += 21;
		ptr1 = saveptr;

		// SEE IF OUR CALL - DONT WANT TO PUT IT IN LIST!

		if (CompareCalls(ptr1, MYCALL))
		{
			// But use it to get route quality setting from other end
			
			// As we now get qual ftom highest, only use this after a reload in
			// case other end has changed.
			if (ROUTE->FirstTimeFlag == 0)
			{
				// Check if route is via our node

				if (memcmp(ptr1, &ptr1[13], 7) == 0)
				{
					if (ROUTE->OtherendLocked == 0)	// Dont update locked quality
						ROUTE->OtherendsRouteQual = ptr1[20];

					ROUTE->FirstTimeFlag = 1;		// Only do it first time after load
				}
			}
			continue;
		}

		if (CheckExcludeList(ptr1) == 0)			// Excluded
			continue;

		for (n = 0; n < NumberofAppls; n++)
		{
			if (CompareCalls(ptr1, APPLCALLTABLE[n].APPLCALL))
				goto IgnoreNode;					// Don't update our applications
		}

		//	MAKE SURE ITS NOT CORRUPTED

		n = 6;

		while (n--)
		{
			if (*(ptr1++) < 0x40)	// Call
			{
				Call1[ConvFromAX25(Msg->ORIGIN, Call1)] = 0;
				Call2[ConvFromAX25(saveptr, Call2)] = 0;
				memcpy(Call3, saveptr + 7,6);
				Call3[6] = 0;
				Debugprintf("Corrupt Node Entry from %s for %s:%s", Call1, Call2, Call3);
				goto IgnoreNode;
			}		
		}
		ptr1++;						// skip ssid

		n = 6;

		while (n--)
		{
			if (*(ptr1) && ((*(ptr1) < 0x20 || *(ptr1) > 0x7A))	)	// Should we accept zeros or convert to spaces??
			{
				Call1[ConvFromAX25(Msg->ORIGIN, Call1)] = 0;
				Call2[ConvFromAX25(saveptr, Call2)] = 0;
				memcpy(Call3, saveptr + 7,6);
				Call3[6] = 0;
				Debugprintf("Corrupt Node Entry from %s for %s:%s", Call1, Call2, Call3);
				goto IgnoreNode;
			}		
			ptr1++;
		}

		ptr1 -= 13;					// Back to start

		// CALCULATE ROUTE QUALITY

		// Experimantal Code to adjust received route qualities based on deduced quality
		// settings at other end of link.

		// Don't mess with Application Qualities. There are almost always 255, and
		// if not there is probably a good reason for the value chosen.

		if (CompareCalls(Msg->ORIGIN, &ptr1[13]))
		{
			// Application Node - Just do normal update

			Qual = (((ROUTEQUAL * ptr1[20]) + 128)) / 256;
		}
		else
		{
			// Try using the highest reported indirect route as remote qual

			if (ROUTE->OtherendLocked == 0)			// Not locked
			{
				if (ptr1[20] > ROUTE->OtherendsRouteQual)
					ROUTE->OtherendsRouteQual = ptr1[20];
			}

			// Treat 255 as 254, so 255 routes doen't get included with minquals 
			//	designed to only include applcalls

			if (ptr1[20] == 255)
				ptr1[20] = 254;	

			Qual = (((ROUTEQUAL * ptr1[20]) + 128)) / 256;

			// I think we should normalize indirect routes twice 
			// as node not only sends differnet qual but adjusts incoming qual with it

			// or should we ............

			// We don't touch appl callsigns so I think everything here is an indirect route

			if (ROUTE->OtherendsRouteQual && PORT->NormalizeQuality)
			{
				Qual = (Qual * ROUTEQUAL) / ROUTE->OtherendsRouteQual;
// not sure about this!	Qual = (Qual * ROUTEQUAL) / ROUTE->OtherendsRouteQual;	// Twice

				if (Qual > ROUTEQUAL)
					Qual = ROUTEQUAL;
			}
		}

		//	SEE IF BELOW MIN QUAL FOR AUTO UPDATES

		if (Qual < MINQUAL)
			continue;

		//	CHECK LINK IS IN DEST LIST
		
		if (FindDestination(ptr1, &DEST) == 0)
		{
			if (DEST == NULL)
				continue;
		
			//	CREATE DESTINATION RECORD

			memset(DEST, 0, sizeof(struct DEST_LIST));
			memcpy(DEST->DEST_CALL, ptr1, 7);
			NUMBEROFNODES++;
		}

		ptr1 += 7;
	
		//	UPDATE ALIAS#

		memcpy(DEST->DEST_ALIAS, ptr1, 6);

		ptr1 += 6;
		
		//	NOW POINTING AT BEST NEIGHBOUR - IF THIS IS US, THEN ROUTE IS A LOOP
	
		if (CompareCalls(ptr1, NETROMCALL))
			Qual = 0;
	
		//	DEST IS NOW IN TABLE -

		//	   1. SEE IF THIS ROUTE IS IN TABLE - IF SO UPDATE QUALITY,
		//	      IF NOT, ADD THIS ONE IF IT HAS HIGHER QUALITY THAN EXISTING ONES


		PROCROUTES(DEST, ROUTE, Qual);
		ptr1 += 8;
IgnoreNode:;
	}
}

VOID PROCROUTES(struct DEST_LIST * DEST, struct ROUTE * ROUTE, int Qual)
{
	//	ADD NEIGHBOUR ADDRESS IN ROUTE TO NODE's ROUTE TABLE IF BETTER QUALITY THAN THOSE PRESENT

	int Index = 0;
	struct NR_DEST_ROUTE_ENTRY Temp;
	
	if (DEST->DEST_STATE & 0x80)			// BBS ENTRY
		return;

	for (Index = 0; Index < 3; Index++)
	{
		if (DEST->NRROUTE[Index].ROUT_NEIGHBOUR == ROUTE)
		{
			if (Index == 0)
			{
				//	THIS IS A REFRESH OF BEST - IF THIS ISNT ACTIVE ROUTE, MAKE IT ACTIVE
			}

			goto UpdatateThisEntry;
		}
	}
				
	//	NOT IN ANY ROUTE

	Index = 0;

	if (DEST->NRROUTE[0].ROUT_NEIGHBOUR == 0)
		goto UpdatateThisEntry;					// SPARE ENTRY, SO USE IT

	if (DEST->NRROUTE[0].ROUT_QUALITY < Qual)
	{
		// New route is better than best, so move other two down and add here

		DEST->NRROUTE[2] = DEST->NRROUTE[1];
		DEST->NRROUTE[1] = DEST->NRROUTE[0];

		DEST->DEST_ROUTE = 0;			// Se we will switch to new one
		
		goto UpdatateThisEntry;
	}

	Index = 1;

	if (DEST->NRROUTE[1].ROUT_NEIGHBOUR == 0)
		goto UpdatateThisEntry;					// SPARE ENTRY, SO USE IT

	if (DEST->NRROUTE[1].ROUT_QUALITY < Qual)
	{
		// New route is better than second, so move down and add here

		DEST->NRROUTE[2] = DEST->NRROUTE[1];
		goto UpdatateThisEntry;
	}

	Index = 2;

	if (DEST->NRROUTE[2].ROUT_NEIGHBOUR == 0)
		goto UpdatateThisEntry;	

	if (DEST->NRROUTE[2].ROUT_QUALITY < Qual)
	{
		// New route is better than third, so  add here
		goto UpdatateThisEntry;
	}

	//	THIS ROUTE IS WORSE THAN ANY OF THE CURRENT 3 - IGNORE IT

	return;


UpdatateThisEntry:

	DEST->NRROUTE[Index].ROUT_NEIGHBOUR = ROUTE;

	//	I DONT KNOW WHY I DID THIS, BUT IT CAUSES REFLECTED ROUTES
	//	TO BE SET UP WITH OBS = 0. THIS MAY PREVENT A VALID ALTERNATE
	//	VIA THE SAME NODE TO FAIL TO BE FOUND. SO I'LL TAKE OUT THE
	//	TEST AND SEE IF ANYTHING NASTY HAPPENS
	//	IT DID - THIS IS ALSO CALLED BY CHECKL3TABLES. TRY RESETING
	//	OBS, BUT NOT QUALITY 


	if ((DEST->NRROUTE[Index].ROUT_OBSCOUNT & 0x80) == 0)
		DEST->NRROUTE[Index].ROUT_OBSCOUNT = OBSINIT;	// SET OBSOLESCENCE COUNT

	if (Qual)
		DEST->NRROUTE[Index].ROUT_QUALITY = Qual;		// IF ZERO, SKIP UPDATE

	//	IT IS POSSIBLE ROUTES ARE NOW OUT OF ORDER

SORTROUTES:

	if (DEST->NRROUTE[1].ROUT_QUALITY > DEST->NRROUTE[0].ROUT_QUALITY)
	{
		//	SWAP 1 AND 2

		Temp = DEST->NRROUTE[0];
		
		DEST->NRROUTE[0] = DEST->NRROUTE[1];
		DEST->NRROUTE[1] = Temp;

		DEST->DEST_ROUTE = 0;		// FORCE A RE-ASSESSMENT
	}

	if (DEST->NRROUTE[2].ROUT_QUALITY > DEST->NRROUTE[1].ROUT_QUALITY)
	{
		//	SWAP 2 AND 3 

		Temp = DEST->NRROUTE[1];
		
		DEST->NRROUTE[1] = DEST->NRROUTE[2];
		DEST->NRROUTE[2] = Temp;
		
		goto SORTROUTES;			//  1 AND 2 MAY NOW BE WRONG!	
	}

	DEST->DEST_ROUTE = 0;			// OForce re-evaluation.
}

int COUNTNODES(struct ROUTE * ROUTE)
{
	//	COUNT NODES WITH ROUTE VIA NEIGHBOUR

	int count = 0;
	int n = MAXDESTS;
	struct DEST_LIST * DEST = DESTS;		// NODE LIST

	while (n--)
	{
		if (DEST->NRROUTE[0].ROUT_NEIGHBOUR == ROUTE)
			count++;
		else if (DEST->NRROUTE[1].ROUT_NEIGHBOUR == ROUTE)
			count++;
		else if (DEST->NRROUTE[2].ROUT_NEIGHBOUR == ROUTE)
			count++;
		else if (DEST->INP3ROUTE[0].ROUT_NEIGHBOUR == ROUTE)
			count++;
		else if (DEST->INP3ROUTE[1].ROUT_NEIGHBOUR == ROUTE)
			count++;
		else if (DEST->INP3ROUTE[2].ROUT_NEIGHBOUR == ROUTE)
			count++;

		DEST++;
	}
	return count;
}

VOID SENDNODE00();
VOID L3BG();



VOID SENDNEXTNODESFRAGMENT();

VOID SENDNODESMSG(int Portnum)
{
	if (NODESINPROGRESS)
		return;

	NODESToOnePort = Portnum;

	if (Portnum)			// Nodes to one port only
	{
		L3CURRENTPORT = GetPortTableEntryFromPortNum(Portnum);

		if (L3CURRENTPORT == 0)
		{
			NODESToOnePort = 0;
			return;
		}
	}
	else
		L3CURRENTPORT = PORTTABLE;
	
	SENDNEXTNODESFRAGMENT();
}

int fragmentCount = 0;		// Node broadcast packets sent to current port

VOID SENDNEXTNODESFRAGMENT()
{
	//	SEND NEXT FRAGMENT OF A NODES BROADCAST

	//	FRAGMENTS ARE SENT AT 10 SECONDS INTERVALS - PARTLY TO REDUCE
	//	QRM, PARTLY TO REDUCE LOAD ON BUFFERS (AND TX POWER SUPPLIES!)

	//	SEND TO PORT IN CURRENTPORT, STARTING AT CURRENTNODE

	struct PORTCONTROL * PORT = L3CURRENTPORT;
	dest_list * DEST = CURRENTNODE;
	MESSAGE * Buffer;
	int Count;
	int Qual;
	int CURRENTPORTNO, TXMINQUAL;
	UCHAR * ptr1, * ptr2;

	if (DEST == 0)
	{
		//	FIRST FRAGMENT TO A PORT

		fragmentCount = 0;

		// Don't send NODES to Shared TX or INP3 Port

		while (PORT->PORTQUALITY == 0 || PORT->TXPORT || PORT->INP3ONLY)
		{
			// No NODES to this port, so go to next

			PORT = PORT->PORTPOINTER;

			if (PORT == NULL || NODESToOnePort)
			{
				// Finished

				NODESToOnePort = 0;
				NODESINPROGRESS = 0;
				return;
			}
		}

		if (NODESToOnePort == 0)			// CurrentPort already set if NODESToOnePort
			L3CURRENTPORT = PORT;

		DEST = CURRENTNODE = DESTS;			// START OF LIST
		NODESINPROGRESS = 1;
	}

	CURRENTPORTNO = PORT->PORTNUMBER; 

	TXMINQUAL = PORT->PORTMINQUAL;

	if (TXMINQUAL == 0)
		TXMINQUAL = 1;						// Dont send zero

	Buffer = GetBuff();

	if (Buffer == 0)
		return;

	Buffer->PORT = CURRENTPORTNO;

	memcpy(Buffer->ORIGIN, NETROMCALL, 7);
	memcpy(Buffer->DEST, NODECALL, 7);

	Buffer->ORIGIN[6] |= 0x61;		// SET CMD END AND RESERVED BITS

	Buffer->CTL = UI;
	Buffer->PID = 0xCF;				// Netrom

	ptr1 = &Buffer->L2DATA[0];
	
	*(ptr1++) = 0xff;		// Nodes Flag

	memcpy(ptr1, MYALIASTEXT, 6);

	ptr1+= 6;

	//	ADD DESTINATION INFO (UNLESS BBS ONLY)

	// If NODE = 0 just send application nodes

	Count = PORT->NODESPACLEN;
	
	if (Count == 0)
		Count = 256;
	
	if (Count < 50)				// STUPIDLY SMALL?
		Count = 50;					// EVEN THIS IS RATHER SILLY

	Count -= 22;					// Fixed Part

	Count /= 21;					// 21 Bytres per entry

	while (Count)
	{
		if (DEST >= ENDDESTLIST)
		{
			CURRENTNODE = 0;			// Finished on this port

			// if sending to only one port then stop

			if (NODESToOnePort)
			{
				NODESToOnePort = 0;
				NODESINPROGRESS = 0;
			}
			else
			{
				L3CURRENTPORT = PORT->PORTPOINTER;
				if (L3CURRENTPORT == NULL)
				{
					// Finished

					NODESINPROGRESS = 0;
				}
			}
			goto Sendit;
		}

		if (DEST->DEST_CALL[0] != 0x40 && DEST->NRROUTE[0].ROUT_QUALITY >= TXMINQUAL &&
			DEST->NRROUTE[0].ROUT_OBSCOUNT >= OBSMIN &&
			(NODE == 1 || DEST->DEST_STATE & 0x80))			// Only send appl nodes if NODE = 0;
		{		
			// Send it
			
			ptr2 = &DEST->DEST_CALL[0];
			memcpy(ptr1, ptr2, 13);				// Dest and Alias
			ptr1 += 13;

			ptr2 = (UCHAR *)DEST->NRROUTE[0].ROUT_NEIGHBOUR;
			
			if (ptr2 == 0)
		
				//	DUMMY POINTER IN BBS ENTRY - PUT IN OUR CALL

				ptr2 = MYCALL;
		
			memcpy(ptr1, ptr2, 7);				// Neighbour Call
			ptr1 += 7;
	
			Qual = 100;

			if (DEST->NRROUTE[0].ROUT_NEIGHBOUR && DEST->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_PORT == CURRENTPORTNO)
			{
				//	BEST NEIGHBOUR IS ON CURRENT PORT - REDUCE QUALITY BY QUAL_ADJUST

				Qual -= PORT->QUAL_ADJUST;
			}

			Qual *= DEST->NRROUTE[0].ROUT_QUALITY;
			Qual /= 100;

			*(ptr1++) = (UCHAR)Qual;

			if (Qual == 0)
				continue;

			Count--;
		}
		DEST++;
	}

	CURRENTNODE = DEST;

Sendit:

	Buffer->LENGTH = (int)(ptr1 - (UCHAR *)Buffer);

	if (Buffer->LENGTH > 35 || fragmentCount == 0)		// Always send first even if no other nodes
	{
		if (PORT->TNC && PORT->TNC->Hardware == H_VARA)
			SendVARANetromNodes(PORT->TNC, Buffer);
		else
			PUT_ON_PORT_Q(PORT, Buffer);

		fragmentCount++;
	}
	else
		ReleaseBuffer(Buffer);

}

VOID L3LINKCLOSED(struct _LINKTABLE * LINK, int Reason)
{
	//	L2 SESSION HAS SHUT DOWN (PROBABLY DUE TO INACTIVITY)
	
	struct ROUTE * ROUTE;
	
	//	CLEAR NEIGHBOUR

	ROUTE = LINK->NEIGHBOUR;			// TO NEIGHBOUR

	if (ROUTE)
	{
		LINK->NEIGHBOUR = NULL;			// Clear links
		ROUTE->NEIGHBOUR_LINK = NULL;

		CLEARACTIVEROUTE(ROUTE, Reason);		// CLEAR ASSOCIATED DEST ENTRIES
	}
}

VOID CLEARACTIVEROUTE(struct ROUTE * ROUTE, int Reason)
{
	//	FIND ANY DESINATIONS WITH ROUTE AS ACTIVE NEIGHBOUR, AND
	//	SET INACTIVE

	dest_list * DEST;
	int n;

	// If a link restarts and is no longer inp3 we must still clear any inp3 routes

//	if (Reason != NORMALCLOSE || ROUTE->INP3Node)
		TellINP3LinkGone(ROUTE);
	
	DEST = DESTS;
	n = MAXDESTS;

	DEST--;							// So we can increment at start of loop

	while (n--)
	{
		DEST++;

		if (DEST->DEST_ROUTE == 0)
			continue;

		if (DEST->INP3ROUTE[DEST->DEST_ROUTE].ROUT_NEIGHBOUR == ROUTE)   // Is this the active route
		{
			// Yes, so clear

			DEST->DEST_ROUTE = 0;			// SET NO ACTIVE ROUTE
		}
	}
}


VOID L3TimerProc()
{
	int i;
	struct PORTCONTROL * PORT = PORTTABLE;
	struct ROUTE * ROUTE;

	struct _LINKTABLE * LINK;

	//	CHECK FOR EXCESSIVE BUFFERS QUEUED AT LINK LEVEL

	if (QCOUNT < 100)
	{
		LINK = LINKS;
		i = MAXLINKS;

		
		while (i--)
		{
			if (LINK->LINKTYPE == 3)		// Only if Internode
			{
				if (COUNT_AT_L2(LINK) > 50)
				{
					Debugprintf("Excessive L3 Queue");
					L3LINKCLOSED(LINK, LINKSTUCK);			// REPORT TO LEVEL 3
					CLEAROUTLINK(LINK);
				}
			}
			LINK++;
		}
	}

	STATSTIME++;

	if (IDTIMER)				// Not if Disabled
	{
		IDTIMER--;

		if (IDTIMER == 0)
		{
			IDTIMER = IDINTERVAL;
			SENDIDMSG();
		}
	}

	//	CHECK FOR BEACON

	if (BTTIMER)				// Not if Disabled
	{
		BTTIMER--;

		if (BTTIMER == 0)
		{
			BTTIMER = BTINTERVAL;
			SENDBTMSG();
		}
	}

	//	CHECK FOR NODES BROADCAST

	if (L3TIMER)				// Not if Disabled
	{
		L3TIMER--;

		if (L3TIMER == 0)
		{
			//	UPDATE DEST LIST AND SEND 'NODES' MESSAGE
			
			L3TIMER = L3INTERVAL;
			UPDATEDESTLIST();
			SENDNODESMSG(0);
		}
	}

	//	TIDY ROUTES

	ROUTE = NEIGHBOURS;
	i = MAXNEIGHBOURS;

	ROUTE--;

	while (i--)
	{
		ROUTE++;

		if (ROUTE->NEIGHBOUR_FLAG)				// Locked?
			continue;

		if (ROUTE->NEIGHBOUR_LINK)				// Has an active Session
			continue;

		if (COUNTNODES(ROUTE) == 0)				// NODES USING THIS DESTINATION
		{
			// IF NUMBER USING ROUTE IS ZERO, DELETE IT

			memset(ROUTE, 0, sizeof (struct ROUTE));
		}
	}
}

VOID L3FastTimer()
{
	//	CALLED ONCE PER SECOND - USED ONLY TO SEND NEXT PART OF A NODES OR
	//	ID MESSAGE SEQUENCE

	MESSAGE * Msg;
	struct PORTCONTROL * PORT = PORTTABLE;

	// Not if Node is closing

	if (CLOSING)
		return;

	INP3TIMER();

	// Send Node faster if VARA

	if (NODESINPROGRESS && L3CURRENTPORT->TNC && L3CURRENTPORT->TNC->NetRomMode)
		SENDNEXTNODESFRAGMENT();

	L3_10SECS--;

	if (L3_10SECS == 0)
	{
		L3_10SECS = 10;
	
		if (IDMSG_Q)				// ID/BEACON TO SEND
		{
			Msg = Q_REM(&IDMSG_Q);

			PORT = GetPortTableEntryFromPortNum(Msg->PORT);

			if (PORT && PORT->TXPORT == 0)			// DONT SEND IF SHARED TX
				PUT_ON_PORT_Q(PORT, Msg);
			else
				ReleaseBuffer(Msg);
		}

		if (NODESINPROGRESS)
			SENDNEXTNODESFRAGMENT();
	}
}


VOID UPDATEDESTLIST()
{
	//	DECREMENT OBS COUNTERS ON EACH ROUTE, AND REMOVE 'DEAD' ENTRIES
	
	dest_list * DEST;
	int n;

	DEST = DESTS;
	n = MAXDESTS;

	DEST--;							// So we can increment at start of loop

	while (n--)
	{
		DEST++;

		if (DEST->DEST_CALL[0] == 0)	// SPARE ENTRY
			continue;

		if (DEST->DEST_STATE & 0x80)	// LOCKED DESTINATION
			continue;

UPDEST000:

		if (DEST->NRROUTE[0].ROUT_NEIGHBOUR == 0)		 // NO DESTINATIONS - DELETE ENTRY unless inp3 routes
		{
			//	 Any INP3 Routes?

			if (DEST->INP3ROUTE[0].ROUT_NEIGHBOUR == 0)
			{
				//	NO ROUTES LEFT TO DEST - REMOVE IT

				REMOVENODE(DEST);						// Unchain, Clear queue and zap
			}
			continue;			
		}

		if (DEST->NRROUTE[0].ROUT_OBSCOUNT == 0)
		{
			//	 FAILED IN USE - DELETE
			
			MOVEALL(DEST);
			goto UPDEST000; //LOOP BACK TO PROCESS MOVED ENTRIES
		}

		if ((DEST->NRROUTE[0].ROUT_OBSCOUNT & 0x80) == 0)	// Locked?
		{
			DEST->NRROUTE[0].ROUT_OBSCOUNT--;

			if (DEST->NRROUTE[0].ROUT_OBSCOUNT == 0)		// Timed out
			{
				MOVEALL(DEST);
				goto UPDEST000; //LOOP BACK TO PROCESS MOVED ENTRIES
			}
		}
	
		// Process Next Neighbour

UPDEST010:

		if (DEST->NRROUTE[1].ROUT_NEIGHBOUR == 0)
			continue;				// NO MORE DESTINATIONS

		if (DEST->NRROUTE[1].ROUT_OBSCOUNT == 0)
		{
			//	 FAILED IN USE - DELETE
			
			MOVE3TO2(DEST);
			goto UPDEST010;			//LOOP BACK TO PROCESS MOVED ENTRIES
		}

		if ((DEST->NRROUTE[1].ROUT_OBSCOUNT & 0x80) == 0)	// Locked?
		{
			DEST->NRROUTE[1].ROUT_OBSCOUNT--;

			if (DEST->NRROUTE[1].ROUT_OBSCOUNT == 0)		// Timed out
			{
				MOVE3TO2(DEST);
				goto UPDEST010; //LOOP BACK TO PROCESS MOVED ENTRIES
			}
		}
	
		// Process Next Neighbour

		if (DEST->NRROUTE[2].ROUT_NEIGHBOUR == 0)
			continue;				// NO MORE DESTINATIONS

		if (DEST->NRROUTE[2].ROUT_OBSCOUNT == 0)
		{
			//	 FAILED IN USE - DELETE
			
			CLEARTHIRD(DEST);
			continue;
		}

		if ((DEST->NRROUTE[2].ROUT_OBSCOUNT & 0x80) == 0)	// Locked?
		{
			DEST->NRROUTE[2].ROUT_OBSCOUNT--;

			if (DEST->NRROUTE[2].ROUT_OBSCOUNT == 0)		// Timed out
			{
				CLEARTHIRD(DEST);
				continue;
			}
		}
	}
}


VOID MOVEALL(dest_list * DEST)
{
	DEST->NRROUTE[0] = DEST->NRROUTE[1];
	MOVE3TO2(DEST);
}

VOID MOVE3TO2(dest_list * DEST)
{
	DEST->NRROUTE[1] = DEST->NRROUTE[2];
	CLEARTHIRD(DEST);
}

VOID CLEARTHIRD(dest_list * DEST)
{
	memset(&DEST->NRROUTE[2], 0, sizeof (struct NR_DEST_ROUTE_ENTRY));
	DEST->DEST_ROUTE = 0;		// CANCEL ACTIVE ROUTE, SO WILL RE-ASSESS BEST
}

// L4 Flags Values

#define DISCPENDING	8		// SEND DISC WHEN ALL DATA ACK'ED

VOID REMOVENODE(dest_list * DEST)
{
	TRANSPORTENTRY * L4 = L4TABLE;
	int n = MAXCIRCUITS;

	//	Remove a node, either because routes have gone, or APPL API has invalidated it

	while (DEST->DEST_Q)
		ReleaseBuffer(Q_REM(&DEST->DEST_Q));

	if (DEST->DEST_STATE & 0x80)	// LOCKED DESTINATION
		return;

	//	MAY NEED TO CHECK FOR L4 CIRCUITS USING DEST, BUT PROBABLY NOT,
	//	 AS THEY SHOULD HAVE TIMED OUT LONG AGO

	// Not necessarily true with INP3, so had better check

	while (n--)
	{
		if (L4->L4USER[0])
		{
			if (L4->L4TARGET.DEST == DEST)
			{
				// Session to/from this Dest

				TRANSPORTENTRY * Partner = L4->L4CROSSLINK;
				char Nodename[20];

				Nodename[DecodeNodeName(DEST->DEST_CALL, Nodename)] = 0;		// null terminate

				Debugprintf("Delete Node for %s Called with active L4 Session - State %d",
					Nodename, L4->L4STATE);

				if (Partner)
				{	
					// if connnecting, send error message and drop back to command level

					if (L4->L4STATE == 2)
					{
						struct DATAMESSAGE * Msg = GetBuff();
						Partner->L4CROSSLINK = 0;		// Back to command lewel

						if (Msg)
						{
							UCHAR * ptr1;
		
							Msg->PID = 0xf0;
							ptr1 = SetupNodeHeader(Msg);
							ptr1 += sprintf(ptr1, "Error - Node %s has disappeared\r", Nodename);

							Msg->LENGTH = (int)(ptr1 - (UCHAR *)Msg);
							C_Q_ADD(&Partner->L4TX_Q, Msg);
							PostDataAvailable(Partner);
						}
					}
					else
					{
						// Failed in session - treat as if a L4DREQ received

						CLOSECURRENTSESSION(Partner);
					}
				}
				CLEARSESSIONENTRY(L4);
			}
		}
		L4++;
	}
	memset(DEST, 0, sizeof(struct DEST_LIST));	
	NUMBEROFNODES--;
}

VOID L3LINKSETUPFAILED(struct _LINKTABLE * LINK)
{
	//	L2 LINK SETUP HAS FAILED - SEE IF ANOTHER NEIGHBOUR CAN BE USED

	struct PORTCONTROL * PORT = PORTTABLE;
	struct ROUTE * ROUTE;

	ROUTE = LINK->NEIGHBOUR;		// TO NEIGHBOUR
	
	if (ROUTE == NULL)
		return;						// NOTHING ???

	if (ROUTE->INP3Node)
	{
		char Normcall[10];
		Normcall[ConvFromAX25(ROUTE->NEIGHBOUR_CALL, Normcall)] = 0;
		Debugprintf("INP3 Route to %s connect failed", Normcall);
	}

	TellINP3LinkSetupFailed(ROUTE);

	ROUTE->NEIGHBOUR_LINK = 0;		// CLEAR IT

	L3TRYNEXTDEST(ROUTE);			// RESET ASSOCIATED DEST ENTRIES
}



VOID L3TRYNEXTDEST(struct ROUTE * ROUTE)
{
	//	FIND ANY DESINATIONS WITH ROUTE AS ACTIVE NEIGHBOUR, AND
	//	SET NEXT BEST NEIGHBOUR (IF ANY) ACTIVE

	int n = MAXDESTS;
	struct DEST_LIST * DEST = DESTS;		// NODE LIST
	int ActiveRoute;

	while (n--)
	{
		ActiveRoute = DEST->DEST_ROUTE;
		
		if (ActiveRoute)
		{
			ActiveRoute --;			// Routes numbered 1 - 6, index from 0
			
			if (DEST->NRROUTE[ActiveRoute].ROUT_NEIGHBOUR == ROUTE)
			{
				// We were best

				//	NEIGHBOUR HAS FAILED - DECREMENT OBSCOUNT
				//	AND TRY TO ACTIVATE ANOTHER (IF ANY)

				if (DEST->NRROUTE[ActiveRoute].ROUT_OBSCOUNT)
				{
					// IF ALREADY ZERO - WILL BE DELETED BY NEXT NODES UPDATE

					if ((DEST->NRROUTE[ActiveRoute].ROUT_OBSCOUNT & 0x80) == 0)
					{
						// not Locked

						if (ActiveRoute < 3)				// Not INP3 Route
							DEST->NRROUTE[ActiveRoute].ROUT_OBSCOUNT--;

						// if ROUTE HAS EXPIRED - WE SHOULD CLEAR IT, AND MOVE OTHERS (IF ANY) UP
					}
				}

				//	REMOVE FIRST MESSAGE FROM DEST_Q. L4 WILL RETRY - IF IT IS LEFT HERE
				//	WE WILL TRY TO ACTIVATE THE DESTINATION FOR EVER

				if (DEST->DEST_Q)
					ReleaseBuffer(Q_REM(&DEST->DEST_Q));

				DEST->DEST_ROUTE++;			// TO NEXT
				
				if (DEST->DEST_ROUTE = 7)
					DEST->DEST_ROUTE = 1;	// TRY TO ACTIVATE FIRST
			}
		}
		DEST++;
	}
}

VOID CHECKNEIGHBOUR(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg)
{
	//	MESSAGE RECEIVED ON LINK WITH NO ROUTE - SET ONE UP

	struct ROUTE * ROUTE;
	struct PORTCONTROL * PORT = LINK->LINKPORT;
	int Portno = PORT->PORTNUMBER;

	if (FindNeighbour(LINK->LINKCALL, Portno, &ROUTE) == 0)
	{
		if (ROUTE == 0)
			goto L3CONN08;			// TABLE FULL??
		
		// FIRST MAKE SURE WE ARE ALLOWING NETWORK ACTIVITY ON THIS PORT

		if (PORT->PORTQUALITY == 0 && PORT->INP3ONLY == 0)
			return;

		ROUTE->NEIGHBOUR_QUAL = PORT->PORTQUALITY;
		ROUTE->INP3Node = PORT->INP3ONLY;

		memcpy(ROUTE->NEIGHBOUR_CALL, LINK->LINKCALL, 7);

		ROUTE->NEIGHBOUR_PORT = Portno;
		ROUTE->NoKeepAlive = PORT->PortNoKeepAlive;
	}

	//	SET THIS AS ACTIVE LINK IF NONE PRESENT

	if (ROUTE->NEIGHBOUR_LINK == 0)
	{
		ROUTE->NEIGHBOUR_LINK = LINK;
		ROUTE->NEIGHBOUR_PORT = Portno;
	}

L3CONN08:

	LINK->NEIGHBOUR = ROUTE;		// SET LINK - NEIGHBOUR
}

struct DEST_LIST * CHECKL3TABLES(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg)
{
	//	CHECK THAT FAR NODE IS IN 'NODES'.
	//	RETURNS POINTER TO NEIGHBOUR ENTRY

	struct ROUTE * ROUTE;
	struct DEST_LIST * DEST;
	int Qual;

	ROUTE = LINK->NEIGHBOUR;

	if (FindDestination(Msg->L3SRCE, &DEST))
		return DEST;					// Ok
	
	if (DEST == NULL)
		return NULL;				// Tsble Full

	//	ADD DESTINATION VIA NEIGHBOUR, UNLESS ON BLACK LIST

#ifdef EXCLUDEBITS

	if (CheckExcludeList(Msg->L3SRCE) == 0)
		return 0;
	
#endif

	memcpy(DEST->DEST_CALL, Msg->L3SRCE, 7);
	
	NUMBEROFNODES++;

	//	MAKE SURE NEIGHBOUR IS DEFINED FOR DESTINATION

	//	IF NODE is NEIGHBOUR, THEN CAN USE NEIGHBOUR QUALITY,
	//	   OTHERWISE WE DONT KNOW ROUTE, SO MUST SET QUAL TO 0


	Qual = 0;						// DONT KNOW ROUTING, SO SET QUALITY TO ZERO
	
	PROCROUTES(DEST, ROUTE, Qual);	// ADD NEIGHBOUR  IF NOT PRESENT
	
	if (DEST->DEST_ROUTE == 0)
	{
		//	MAKE CURRENT NEIGHBOUR ACTIVE

		int n = 0;

		DEST->DEST_ROUTE = 1;

		while (n < 3)
		{
			if (DEST->NRROUTE[n].ROUT_NEIGHBOUR == ROUTE)
				break;

			DEST->DEST_ROUTE++;
			n++;
		}

		if (DEST->DEST_ROUTE > 3)
		{
			DEST->DEST_ROUTE = 1;	// CURRENT NEIGHBOUR ISNT IN DEST LIST - SET TO USE BEST
			return DEST;			// Can't update OBS
		}
	}

	//	REFRESH OBS COUNT

	if (DEST->DEST_ROUTE)
	{
		int Index = DEST->DEST_ROUTE -1;

		if (Index > 2)				// INP3 Route
			return DEST;

		if (DEST->NRROUTE[Index].ROUT_OBSCOUNT & 0x80)		// Locked:
			return DEST;

		DEST->NRROUTE[Index].ROUT_OBSCOUNT = OBSINIT;
	}
	return DEST;
}

VOID REFRESHROUTE(TRANSPORTENTRY * Session)
{
	//	RESET OBS COUNT ON CURRENT ROUTE TO DEST FOR SESSION IN [EBX]
	//	CALLED WHEN INFO ACK RECEIVED, INDICATING ROUTE IS STILL OK

	struct DEST_LIST * DEST;
	int Index;

	DEST = Session->L4TARGET.DEST;

	if (DEST == 0)
		return;					// No Dest ???

	Index = DEST->DEST_ROUTE;

	if (Index == 0)
		return;					// NONE ACTIVE???

	Index--;

	if (Index > 2)				// INP3 Route
		return;

	if (DEST->NRROUTE[Index].ROUT_OBSCOUNT & 0x80)
		return;					// Locked

	DEST->NRROUTE[Index].ROUT_OBSCOUNT = OBSINIT;
}


