/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    fullrtmp.c

Abstract:

     Handle the RTMP protocol for a routing node.

Author:

    Garth Conboy     (Pacer Software)
    Nikhil Kamkolkar (NikhilK)

Revision History:

     GC - (03/13/91): Added the "recentRoutes" cache to improve large network
                      routing performance.
     GC - (01/20/92): Removed usage of numberOfConfiguredPorts; portDescriptors
                      may now be sparse, we use the portActive flag instead.
     GC - (01/21/92): Made RemoveFromRoutingTable externally visible.
     GC - (03/31/92): Updated for BufferDescriptors.
     GC - (06/15/92): Correct "notify neighbor" handling: don't allow route
                      upgrading.

--*/


#define IncludeFullRtmpErrors 1
#include "atalk.h"

#if IamNot an AppleTalkRouter
  // Empty file
#else

ExternForVisibleFunction TimerHandler RtmpSendTimerExpired;

ExternForVisibleFunction TimerHandler RtmpValidityTimerExpired;

ExternForVisibleFunction Boolean
        GetOrSetNetworkNumber(int port,
                              unsigned int suggestedNetworkNumber);

ExternForVisibleFunction void SendRoutingData(int port,
                                              AppleTalkAddress destination,
                                              Boolean doSplitHorizon);

ExternForVisibleFunction void
     EnterIntoRoutingTable(AppleTalkNetworkRange networkRange,
                           int port,
                           ExtendedAppleTalkNodeNumber nextRouter,
                           int numberOfHops);

static Boolean firstCall = True;




Boolean far StartRtmpProcessingOnPort(int port,
                                      ExtendedAppleTalkNodeNumber routerNode)
{
	
	  //
	  // For extended ports there is very little to do now; the process of getting
	  //   the node has done most of our work for us.
	  //
	
	  if (GET_PORTDESCRIPTOR(port)->ExtendedNetwork) {
		 //
		 // If we've seen another router; does the real network agree with what
		 //   we were going to try to seed?  No seeding for half ports.
		 //
	
		 if (GET_PORTDESCRIPTOR(port)->PortType isnt NonAppleTalkHalfPort and
			 GET_PORTDESCRIPTOR(port)->SeenRouterRecently and
			 GET_PORTDESCRIPTOR(port)->InitialNetworkRange.FirstNetworkNumber isnt
				  UnknownNetworkNumber)
			if (GET_PORTDESCRIPTOR(port)->InitialNetworkRange.FirstNetworkNumber isnt
				GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber or
				GET_PORTDESCRIPTOR(port)->InitialNetworkRange.LastNetworkNumber isnt
				GET_PORTDESCRIPTOR(port)->ThisCableRange.LastNetworkNumber)
			   ErrorLog("StartRtmpProcessingOnPort", ISevWarning, __LINE__, port,
						IErrFullRtmpIgnoredNetRange, IMsgFullRtmpIgnoredNetRange,
						Insert0());
	
		 // Otherwise, we're the first rotuer, so we'll seed (if we can).
	
		 if (GET_PORTDESCRIPTOR(port)->PortType is NonAppleTalkHalfPort) {
			GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber =
				  UnknownNetworkNumber;
			GET_PORTDESCRIPTOR(port)->ThisCableRange.LastNetworkNumber =
				  UnknownNetworkNumber;
		 }
		 else if (not GET_PORTDESCRIPTOR(port)->SeedRouter) {
			ErrorLog("StartRtmpProcessingOnPort", ISevError, __LINE__, port,
					 IErrFullRtmpNoSeedCantStart, IMsgFullRtmpNoSeedCantStart,
					 Insert0());
			return(False);
		 }
		 else if (not GET_PORTDESCRIPTOR(port)->SeenRouterRecently)
			GET_PORTDESCRIPTOR(port)->ThisCableRange =
					   GET_PORTDESCRIPTOR(port)->InitialNetworkRange;
	  }
	  else {
		 //
		 // For non-extended networks, we need to either find or seed our network
		 //   number.
		 //
	
		 if (not GetOrSetNetworkNumber(port, (unsigned int)(
										 GET_PORTDESCRIPTOR(port)->SeedRouter ?
										 GET_PORTDESCRIPTOR(port)->InitialNetworkRange.
											 FirstNetworkNumber :
										 UnknownNetworkNumber)))
			return(False);
	
		 if (not GET_PORTDESCRIPTOR(port)->SeenRouterRecently)
			GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber =
					 GET_PORTDESCRIPTOR(port)->InitialNetworkRange.FirstNetworkNumber;
	
		 //
		 // On non-extended networks, we would have "allocated" a node with network
		 //   zero, fix this up here!
		 //
	
		 GET_PORTDESCRIPTOR(port)->ActiveNodes->ExtendedNode.NetworkNumber =
			 GET_PORTDESCRIPTOR(port)->ARouter.NetworkNumber =
				  routerNode.NetworkNumber =
					   GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber;
	  }
	
	  // Well, we're a router now, so mark us as A-ROUTER.
	
	  GET_PORTDESCRIPTOR(port)->SeenRouterRecently = True;
	  GET_PORTDESCRIPTOR(port)->ARouter = routerNode;
	  GET_PORTDESCRIPTOR(port)->RouterRunning = True;
	
	  // Okay, make it so...
	
	  if (GET_PORTDESCRIPTOR(port)->PortType isnt NonAppleTalkHalfPort)
		 EnterIntoRoutingTable(GET_PORTDESCRIPTOR(port)->ThisCableRange, port,
							   routerNode, 0);
	
	  // Switch the incoming RTMP packet handler to be the router version.
	
	  CloseSocketOnNodeIfOpen(port, routerNode, RtmpSocket);
	  if (OpenSocketOnNode(empty, port, &routerNode, RtmpSocket, RtmpPacketInRouter,
						   (long)0, False, empty, 0, empty) isnt ATnoError) {
		 ErrorLog("StartRtmpProcessingOnPort", ISevError, __LINE__, port,
				  IErrFullRtmpBadSocketOpen, IMsgFullRtmpBadSocketOpen,
				  Insert0());
		 return(False);
	  }
	
	  // Start the two RTMP timers.
	
	  if (firstCall) {
		 StartTimer(RtmpSendTimerExpired, RtmpSendTimerSeconds, 0, empty);
		 StartTimer(RtmpValidityTimerExpired, RtmpValidityTimerSeconds, 0, empty);
		 firstCall = False;
	  }
	
	  return(True);
	
}  // StartRtmpProcessingOnPort




void far ShutdownFullRtmp(void)
{
	
	  firstCall = True;
	
}  // ShutdownFullRtmp




long far RtmpPacketInRouter(AppleTalkErrorCode errorCode,
                            long unsigned userData,
                            int port,
                            AppleTalkAddress source,
                            long destinationSocket,
                            int protocolType,
                            char far *datagram,
                            int datagramLength,
                            AppleTalkAddress actualDestination)
{
	AppleTalkNetworkRange cableRange;
	int rtmpCommand, responseSize, numberOfHops, index, indexToo;
	RoutingTableEntry routingTableEntry;
	ExtendedAppleTalkNodeNumber nextRouter;
	Boolean foundOverlap;
	BufferDescriptor buffer;
	
	// "Use" unneeded formals.
	
	actualDestination, userData;
	
	// Do we care?
	
	if (errorCode is ATsocketClosed)
	   return((long)True);
	else if (errorCode isnt ATnoError) {
		ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
				IErrFullRtmpBadIncomingError, IMsgFullRtmpBadIncomingError,
				Insert0());
		return((long)True);
	}
	
	// Is the packet long enough to have any interestng data?
	
	if (protocolType is DdpProtocolRtmpRequest) {
		if (datagramLength < RtmpRequestDatagramSize)
		{
		  ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
				   IErrFullRtmpReqTooShort, IMsgFullRtmpReqTooShort,
				   Insert0());
		  return((long)True);
		}
	}
	else if (protocolType is DdpProtocolRtmpResponseOrData) {
		if ((GET_PORTDESCRIPTOR(port)->ExtendedNetwork and
			datagramLength < RtmpDataMinimumSizeExtended) or
		   (not GET_PORTDESCRIPTOR(port)->ExtendedNetwork and
			datagramLength < RtmpDataMinimumSizeNonExtended)) {
		  ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
				   IErrFullRtmpDataTooShort, IMsgFullRtmpDataTooShort,
				   Insert0());
		  return((long)True);
		}
	}
	else
	   return((long)True);     // Funny protocol type...
	
	// We're going to be playing with the routing tables...
	
	DeferTimerChecking();
	DeferIncomingPackets();
	
	if (protocolType is DdpProtocolRtmpRequest) {
		
		   rtmpCommand = (unsigned char)datagram[RtmpRequestCommandOffset];
		   switch (rtmpCommand) {
			  case RtmpRequestCommand:
				 break;
		
			  case RtmpDataRequestCommand:
			  case RtmpEntireDataRequestCommand:
		
			   //
				 // Send all or part of our routing tables back to the requesting
				 //   address...
			   //
		
				 SendRoutingData(port, source,
								 (Boolean)(rtmpCommand is RtmpDataRequestCommand));
				 HandleIncomingPackets();
				 HandleDeferredTimerChecks();
				 return((long)True);
		
			  default:
				 ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
						  IErrFullRtmpBadReqCommand, IMsgFullRtmpBadReqCommand,
						  Insert0());
				 HandleIncomingPackets();
				 HandleDeferredTimerChecks();
				 return((long)True);
		   }
		
			//
		   // Okay, we're a standard RtmpRequest; do the right thing.  Find our
		   //   port's entry in the routing table.
			//
		
		   if ((routingTableEntry =
				FindInRoutingTable(GET_PORTDESCRIPTOR(port)->ARouter.
								   networkNumber)) is empty) {
			  ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
					   IErrFullRtmpBadRoutingTables, IMsgFullRtmpBadRoutingTables,
					   Insert0());
			  HandleIncomingPackets();
			  HandleDeferredTimerChecks();
			  return((long)True);
		   }
		
		   // This guy would like an RTMP response...
		
		   if ((buffer = NewBufferDescriptor(RtmpResponseMaxSize)) is Empty) {
			  ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
					   IErrFullRtmpOutOfMemory, IMsgFullRtmpOutOfMemory,
					   Insert0());
			  HandleIncomingPackets();
			  HandleDeferredTimerChecks();
			  return((long)True);
		   }
		
		   buffer->data[RtmpSendersNetworkOffset] =
				(char)((GET_PORTDESCRIPTOR(port)->ARouter.NetworkNumber >> 8) & 0xFF);
		   buffer->data[RtmpSendersNetworkOffset + 1] =
				(char)(GET_PORTDESCRIPTOR(port)->ARouter.NetworkNumber & 0xFF);
		   buffer->data[RtmpSendersIdLengthOffset] = 8;  // Bits
		   buffer->data[RtmpSendersIdOffset] = GET_PORTDESCRIPTOR(port)->ARouter.NodeNumber;
		
			//
		   // On an extended port, we also want to add the initial network range
		   //   tuple.
			//
		
		   if (not GET_PORTDESCRIPTOR(port)->ExtendedNetwork)
			  responseSize = RtmpSendersIdOffset + sizeof(char);
		   else {
			  buffer->data[RtmpRangeStartOffset] =
					(char)((GET_PORTDESCRIPTOR(port)->ThisCableRange.
							FirstNetworkNumber >> 8) & 0xFF);
			  buffer->data[RtmpRangeStartOffset + 1] =
					(char)(GET_PORTDESCRIPTOR(port)->ThisCableRange.
						   FirstNetworkNumber & 0xFF);
			  buffer->data[RtmpRangeStartOffset + 2] = RtmpTupleWithRange;
			  buffer->data[RtmpRangeEndOffset] =
					(char)((GET_PORTDESCRIPTOR(port)->ThisCableRange.
							LastNetworkNumber >> 8) & 0xFF);
			  buffer->data[RtmpRangeEndOffset + 1] =
					(char)(GET_PORTDESCRIPTOR(port)->ThisCableRange.
						   LastNetworkNumber & 0xFF);
			  responseSize = RtmpRangeEndOffset + sizeof(short unsigned);
		   }
		
		   // Send the response.
		
		   if (DeliverDdp(destinationSocket, source, DdpProtocolRtmpResponseOrData,
						  buffer, responseSize, Empty, Empty, 0) isnt ATnoError)
			  ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
					   IErrFullRtmpBadRespSend, IMsgFullRtmpBadRespSend,
					   Insert0());
		
	}
	else if (protocolType is DdpProtocolRtmpResponseOrData) {
		if ((unsigned char)datagram[RtmpSendersIdLengthOffset] isnt 8)
		{
		  ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
				   IErrFullRtmpBadNodeIdLen, IMsgFullRtmpBadNodeIdLen,
				   Insert0());
		  HandleIncomingPackets();
		  HandleDeferredTimerChecks();
		  return((long)True);
		}
	
		// For non extended networks, we should have a leading version stamp.
	
		if (GET_PORTDESCRIPTOR(port)->ExtendedNetwork)
		  index = RtmpSendersIdOffset + 1;
		else
		  if ((unsigned char)datagram[RtmpSendersIdOffset + 1] isnt 0 or
			  (unsigned char)datagram[RtmpSendersIdOffset + 2] isnt 0 or
			  (unsigned char)datagram[RtmpSendersIdOffset + 3] isnt
				RtmpVersionNumber) {
			 ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
					  IErrFullRtmpBadVersion, IMsgFullRtmpBadVersion,
					  Insert0());
			 HandleIncomingPackets();
			 HandleDeferredTimerChecks();
			 return((long)True);
		  }
		  else
			 index = RtmpSendersIdOffset + 4;
	
		//
		// Walk through the routing tuples... The "+ 3" ensure we as least have
		//   a non-extended tuple.
		//
	
		while (index + 3 <= datagramLength) {
		  // Decode either a short or long tuple...
	
		  cableRange.firstNetworkNumber = (unsigned short)
						  (((unsigned char)datagram[index++]) << 8);
		  cableRange.firstNetworkNumber += (unsigned char)datagram[index++];
		  numberOfHops = (unsigned char)datagram[index++];
		  if (numberOfHops & RtmpExtendedTupleMask) {
			 if (index + 2 > datagramLength)
			 {
				ErrorLog("RtmpPacketInRouter", ISevError, __LINE__, port,
						 IErrFullRtmpBadTuple, IMsgFullRtmpBadTuple,
						 Insert0());
				HandleIncomingPackets();
				HandleDeferredTimerChecks();
				return((long)True);
			 }
			 cableRange.lastNetworkNumber = (unsigned short)
							 (((unsigned char)datagram[index++]) << 8);
			 cableRange.lastNetworkNumber += (unsigned char)datagram[index++];
			 if ((unsigned char)datagram[index++] isnt RtmpVersionNumber) {
				ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
						 IErrFullRtmpBadVersionExt, IMsgFullRtmpBadVersionExt,
						 Insert0());
				HandleIncomingPackets();
				HandleDeferredTimerChecks();
				return((long)True);
			 }
		  }
		  else
			 cableRange.lastNetworkNumber = cableRange.firstNetworkNumber;
		  numberOfHops &= RtmpNumberOfHopsMask;
	
		//
		  // Skip dummy "this net" on half ports; the first tuple coming in
		  //   from a half port will fall into this catagory (having no network
		  //   range).
		//
	
		  if (GET_PORTDESCRIPTOR(port)->PortType is NonAppleTalkHalfPort and
			  cableRange.firstNetworkNumber is UnknownNetworkNumber)
			 continue;
	
		  // Validate what we have...
	
		  if (not CheckNetworkRange(cableRange))
			 continue;
	
		//
		  // Okay, first check to see if this tuple concerns a network range
		  //   that we already know about.
		//
	
		  if ((routingTableEntry =
			   FindInRoutingTable(cableRange.firstNetworkNumber)) isnt empty and
			  routingTableEntry->networkRange.firstNetworkNumber is
					 cableRange.firstNetworkNumber and
			  routingTableEntry->networkRange.lastNetworkNumber is
					 cableRange.lastNetworkNumber) {
			 // Check for "notify neighbor" telling us that an entry is bad.
		
			 if (numberOfHops is 31 and
				 routingTableEntry->nextRouter.networkNumber is
					 source.networkNumber and
				 routingTableEntry->nextRouter.nodeNumber is source.nodeNumber) {
				// Don't "upgrade" route to PrettyBad from Bad...
		
				if (routingTableEntry->entryState isnt Bad)
				   routingTableEntry->entryState = PrettyBad;
				continue;
			 }
		
		   //
			 // If we're hearing about one of our directly connected nets, we
			 //   know best... ignore the info.
		   //
		
			 if (routingTableEntry->numberOfHops is 0)
				continue;
		
		   //
			 // Check for previously bad entry, and a short enough path
			 //   with this tuple... if so, replace the entry.
		   //
		
			 if ((routingTableEntry->entryState is PrettyBad or
				  routingTableEntry->entryState is Bad) and
				 numberOfHops < 15) {
				routingTableEntry->numberOfHops = (short)(numberOfHops + 1);
				routingTableEntry->nextRouter.networkNumber =
						  source.networkNumber;
				routingTableEntry->nextRouter.nodeNumber =
						  source.nodeNumber;
				routingTableEntry->port = (short)port;
				routingTableEntry->entryState = Good;
				continue;
			 }
		
		   //
			 // Check fora shorter or equidistant path to the target network...
			 //   if so, replace the entry.
		   //
		
			 if (routingTableEntry->numberOfHops >= numberOfHops + 1 and
				 numberOfHops < 15) {
				routingTableEntry->numberOfHops = (short)(numberOfHops + 1);
				routingTableEntry->nextRouter.networkNumber =
						  source.networkNumber;
				routingTableEntry->nextRouter.nodeNumber =
						  source.nodeNumber;
				routingTableEntry->port = (short)port;
				routingTableEntry->entryState = Good;
				continue;
			 }
		
		   //
			 // Check for the same router still thinking it has a path to the
			 //   network, but it's further away now... if so, update the entry.
		   //
		
			 if (routingTableEntry->nextRouter.networkNumber is
						  source.networkNumber and
				 routingTableEntry->nextRouter.nodeNumber is
						  source.nodeNumber and
				 routingTableEntry->port is (short)port) {
				routingTableEntry->numberOfHops = (short)(numberOfHops + 1);
				if (routingTableEntry->numberOfHops < 16)
				   routingTableEntry->entryState = Good;
				else
				   RemoveFromRoutingTable(cableRange);
				continue;
			 }
			 continue;
		  }
	
		//
		  // Otherwise, we need to walk through the entire routing table
		  //   making sure the current tuple doesn't overlap with anything
		  //   we already have (since it didn't exactly match) -- if we find
		  //   an overlap, ignore the tuple (a network configuration error,
		  //   no doubt), else add it as a new network range!
		//
	
		  for (foundOverlap = False, indexToo = 0;
			   not foundOverlap and indexToo < NumberOfRtmpHashBuckets;
			   indexToo += 1)
			 for (routingTableEntry = routingTable[indexToo];
				  not foundOverlap and routingTableEntry isnt empty;
				  routingTableEntry = routingTableEntry->next)
				if (RangesOverlap(&cableRange, &routingTableEntry->networkRange))
				   foundOverlap = True;
	
		  if (foundOverlap) {
			 ErrorLog("RtmpPacketInRouter", ISevWarning, __LINE__, port,
					  IErrFullRtmpOverlappingNets, IMsgFullRtmpOverlappingNets,
					  Insert0());
			 continue;
		  }
	
		  // Okay, enter this new network range!
	
		  nextRouter.networkNumber = source.networkNumber;
		  nextRouter.nodeNumber = source.nodeNumber;
		  if (numberOfHops < 15)
			 EnterIntoRoutingTable(cableRange, port, nextRouter,
								   numberOfHops + 1);
	
		}   // Walk through all of the tuples loop.
	
	}  // Protocol type is DdpProtocolRtmpResponseOrData
	
	HandleIncomingPackets();
	HandleDeferredTimerChecks();
	return((long)True);
	
}  // RtmpPacketInRouter




RoutingTableEntry far FindInRoutingTable(short unsigned networkNumber)
{
	int index, recentRouteIndex;
	RoutingTableEntry routingTableEntry;
	
	// First, try the "recent route cache".
	
	CheckMod(recentRouteIndex, networkNumber, NumberOfRecentRouteBuckets,
			 "FindInRoutingTable");
	if (recentRoutes[recentRouteIndex] isnt empty and
		IsWithinNetworkRange(networkNumber,
							 &recentRoutes[recentRouteIndex]->networkRange))
	   return(recentRoutes[recentRouteIndex]);
	
	//
	// Find a given network number in the RTMP routing table.  We maintain the
	//   routing table as a hashed lookup structure based upon the first network
	//   of the given network range.  First, check to see if we're lucky and the
	//   target network number is the start of a range...
	//
	
	CheckMod(index, networkNumber, NumberOfRtmpHashBuckets,
			 "FindInRoutingTable");
	
	for (routingTableEntry = routingTable[index];
		 routingTableEntry isnt empty;
		 routingTableEntry = routingTableEntry->next)
	   if (routingTableEntry->networkRange.firstNetworkNumber is networkNumber)
		  break;
	
	// Cache and return our find!
	
	if (routingTableEntry isnt empty) {
		recentRoutes[recentRouteIndex] = routingTableEntry;
		return(routingTableEntry);
	}
	
	//
	// Otherwise, we need to walk the entire routing table to see if this guy
	//   is within any range!  Sigh.  If found, cache the find.
	//
	
	for (index = 0; index < NumberOfRtmpHashBuckets; index += 1)
	   for (routingTableEntry = routingTable[index];
			routingTableEntry isnt empty;
			routingTableEntry = routingTableEntry->next)
		  if (IsWithinNetworkRange(networkNumber,
								   &routingTableEntry->networkRange)) {
			 recentRoutes[recentRouteIndex] = routingTableEntry;
			 return(routingTableEntry);
		  }
	
	// Oops, nobody home!
	
	return(empty);
	
}  // FindInRoutingTable




Boolean far RemoveFromRoutingTable(AppleTalkNetworkRange networkRange)
{
	int index;
	RoutingTableEntry routingTableEntry, previousRoutingTableEntry = empty;
	
	//
	// Remove a given network number from the RTMP routing table.  Return True
	//   if we can find/remove the network; False otherwise.
	//
	
	CheckMod(index, networkRange.firstNetworkNumber, NumberOfRtmpHashBuckets,
			 "RemoveFromRoutingTable");
	
	for (routingTableEntry = routingTable[index];
		 routingTableEntry isnt empty;
		 routingTableEntry = routingTableEntry->next)
	   if (routingTableEntry->networkRange.firstNetworkNumber is
				networkRange.firstNetworkNumber and
		   routingTableEntry->networkRange.lastNetworkNumber is
				networkRange.lastNetworkNumber) {
		  // Unthread the guy who's leaving...
	
		  if (previousRoutingTableEntry is empty)
			 routingTable[index] = routingTableEntry->next;
		  else
			 previousRoutingTableEntry->next = routingTableEntry->next;
	
		//
		  // Do a pass through the "recent router cache", remove this guy if
		  //   he's there.
		//
	
		  for (index = 0; index < NumberOfRecentRouteBuckets; index += 1)
			 if (recentRoutes[index] is routingTableEntry) {
				recentRoutes[index] = empty;
				break;
			 }
	
		  // Free the zone list and the actual routing table node.
	
		  FreeZoneList(routingTableEntry->zoneList);
		  Free(routingTableEntry);
	
		  // The deed is done.
	
		  return(True);
	   }
	   else
		  previousRoutingTableEntry = routingTableEntry;
	
	return(False);
	
}  // RemoveFromRoutingTable




ExternForVisibleFunction Boolean
        GetOrSetNetworkNumber(int port,
                              unsigned int suggestedNetworkNumber)
{
	// We use this beast for non-extended networks only!
	
	//
	// The plan here is not to mess up a working Internet.  So,
	//   we'll send out a few RTMP request packets, if we can find the network
	//   number of the network, we'll use that one and ignore the one that was
	//   passed in.  Otherwise, we'll use the one that was passed in, unless
	//   that is zero, in which case we'll blow the user away.  A zero value for
	//   our argument indicates that the caller does not want to set the network
	//   network-number and we should expect to find at least one bridge already
	//   operating out there on the specified port.
	//
	
	BufferDescriptor buffer;
	AppleTalkAddress source, destination;
	int numberOfRequests = 0;
	
	if (GET_PORTDESCRIPTOR(port)->ExtendedNetwork) {
		ErrorLog("GetOrSetNetworkNumber", ISevError, __LINE__, port,
				IErrFullRtmpSigh, IMsgFullRtmpSigh,
				Insert0());
		return(False);
	}
	
	// Set up the source, destination, and our very simple request packet.
	
	source.networkNumber = destination.networkNumber = UnknownNetworkNumber;
	source.nodeNumber =
		   GET_PORTDESCRIPTOR(port)->ActiveNodes->ExtendedNode.NodeNumber;
	destination.nodeNumber = AppleTalkBroadcastNodeNumber;
	source.socketNumber = destination.socketNumber = RtmpSocket;
	
	//
	// Blast a number of requests out on the wire, see if anybody will let us in
	//   on the network network-number.
	//
	
	while (not GET_PORTDESCRIPTOR(port)->SeenRouterRecently and
		   numberOfRequests < NumberOfRtmpRequests) {
		// Build the request packet.
	
		if ((buffer = NewBufferDescriptor(RtmpRequestDatagramSize)) is Empty) {
		  ErrorLog("GetOrSetNetworkNumber", ISevError, __LINE__, port,
				   IErrFullRtmpOutOfMemory, IMsgFullRtmpOutOfMemory,
				   Insert0());
		  return(False);
		}
	
		buffer->data[RtmpRequestCommandOffset] = RtmpRequestCommand;
	
		if (not TransmitDdp(port, source, destination, DdpProtocolRtmpRequest,
						   buffer, RtmpRequestDatagramSize, 0, empty, empty,
						   Empty, 0)) {
		  ErrorLog("GetOrSetNetworkNumber", ISevError, __LINE__, port,
				   IErrFullRtmpBadReqSend, IMsgFullRtmpBadReqSend,
				   Insert0());
		  return(False);
		}
		numberOfRequests += 1;
		WaitFor(RtmpRequestTimerInHundreths,
			   &GET_PORTDESCRIPTOR(port)->SeenRouterRecently);
	}
	
	// If we got an answer, we're set...
	
	if (GET_PORTDESCRIPTOR(port)->SeenRouterRecently) {
		if (suggestedNetworkNumber isnt UnknownNetworkNumber and
		   GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber isnt
				(short unsigned)suggestedNetworkNumber)
		  ErrorLog("GetOrSetNetworkNumber", ISevWarning, __LINE__, port,
				   IErrFullRtmpIgnoredNet, IMsgFullRtmpIgnoredNet,
				   Insert0());
		return(True);
	}
	
	//
	// If we didn't get an answer, we had better have a suggested network number
	//   passed in.
	//
	
	if (suggestedNetworkNumber is UnknownNetworkNumber) {
		ErrorLog("GetOrSetNetworkNumber", ISevError, __LINE__, port,
				IErrFullRtmpNoSeedCantStart, IMsgFullRtmpNoSeedCantStart,
				Insert0());
		return(False);
	}
	
	GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber =
				(short unsigned)suggestedNetworkNumber;
	return(True);
	
}  // GetOrSetNetworkNumber




ExternForVisibleFunction void
     EnterIntoRoutingTable(AppleTalkNetworkRange networkRange,
                           int port,
                           ExtendedAppleTalkNodeNumber nextRouter,
                           int numberOfHops)
{
	RoutingTableEntry routingTableEntry;
	int index;
	
	//
	// Make a new entry in our RTMP routing table.  We assume that we are passed
	//   a legitimate new entry (i.e. all range overlaps have already been checked
	//   for).
	//
	
	CheckMod(index, networkRange.firstNetworkNumber, NumberOfRtmpHashBuckets,
			 "EnterIntoRoutingTable");
	
	if (networkRange.lastNetworkNumber is UnknownNetworkNumber)
	   networkRange.lastNetworkNumber = networkRange.firstNetworkNumber;
	
	routingTableEntry = (RoutingTableEntry)Calloc(sizeof(*routingTableEntry), 1);
	routingTableEntry->networkRange = networkRange;
	routingTableEntry->port = (short)port;
	routingTableEntry->nextRouter = nextRouter;
	routingTableEntry->numberOfHops = (short)numberOfHops;
	routingTableEntry->entryState = Good;
	
	// Thread the new entry into the head of the hash list.
	
	routingTableEntry->next = routingTable[index];
	routingTable[index] = routingTableEntry;
	
}  // EnterIntoRoutingTable




ExternForVisibleFunction void far RtmpSendTimerExpired(long unsigned timerId,
                                                       int additionalDataSize,
                                                       char far *additionalData)
{
	AppleTalkAddress destination;
	int port;
	
	// "Use" unneeded actual parameters.
	
	timerId, additionalDataSize, additionalData;
	
	// Broadcast out each port a split horizon view of our routing tables.
	
	destination.networkNumber = CableWideBroadcastNetworkNumber;
	destination.nodeNumber = AppleTalkBroadcastNodeNumber;
	destination.socketNumber = RtmpSocket;
	for (port = 0; port < MaximumNumberOfPorts; port += 1)
	   if (GET_PORTDESCRIPTOR(port)->Flags & PD_ACTIVE and
		   GET_PORTDESCRIPTOR(port)->RouterRunning) {
		  DeferIncomingPackets();
		  SendRoutingData(port, destination, True);
		  HandleIncomingPackets();
	   }
	
	// Re-arm the send RTMP timer...
	
	StartTimer(RtmpSendTimerExpired, RtmpSendTimerSeconds, 0, empty);
	
	// We've done the deed...
	
	HandleDeferredTimerChecks();
	return;
	
}  // RtmpSendTimerExpired




ExternForVisibleFunction void far
     RtmpValidityTimerExpired(long unsigned timerId,
                              int additionalDataSize,
                              char far *additionalData)
{
	RoutingTableEntry routingTableEntry, nextRoutingTableEntry;
	int index;
	
	// "Use" unneeded actual parameters.
	
	timerId, additionalDataSize, additionalData;
	
	//
	// We're going to muck with the routing databases, so defer incoming
	//   packets while we do the deed.
	//
	
	DeferIncomingPackets();
	
	// Walk the routing table aging the entries...
	
	for (index = 0; index < NumberOfRtmpHashBuckets; index += 1)
	   for (routingTableEntry = routingTable[index];
			routingTableEntry isnt empty;
			routingTableEntry = nextRoutingTableEntry) {
		  // Get the next entry first, we may delete the current entry...
	
		  nextRoutingTableEntry = routingTableEntry->next;
	
		  switch (routingTableEntry->entryState) {
			 case Good:
				if (routingTableEntry->numberOfHops isnt 0)
				   routingTableEntry->entryState = Suspect;
				break;
			 case Suspect:
				routingTableEntry->entryState = PrettyBad;
				break;
			 case PrettyBad:
				routingTableEntry->entryState = Bad;
				break;
			 case Bad:
				RemoveFromRoutingTable(routingTableEntry->networkRange);
				break;
			 default:
				ErrorLog("RtmpValidityTimerExpired", ISevError, __LINE__,
						 routingTableEntry->port,
						 IErrFullRtmpBadEntryState, IMsgFullRtmpBadEntryState,
						 Insert0());
				RemoveFromRoutingTable(routingTableEntry->networkRange);
				break;
		  }
	   }
	
	// Re-arm the Validity timer...
	
	StartTimer(RtmpValidityTimerExpired, RtmpValidityTimerSeconds, 0, empty);
	
	// We've done the deed...
	
	HandleIncomingPackets();
	HandleDeferredTimerChecks();
	return;
	
}  // RtmpValidityTimerExpired




ExternForVisibleFunction void SendRoutingData(int port,
                                              AppleTalkAddress destination,
                                              Boolean doSplitHorizon)
{
	RoutingTableEntry routingTableEntry;
	BufferDescriptor datagram;
	int index, hash, staticHeaderSize;
	Boolean newPacket = True, unsentTuples = False;
	AppleTalkAddress source;
	long sourceSocket;
	ExtendedAppleTalkNodeNumber sourceNode;
	
	// First, compute the source socket: Rtmp socket on our routers node.
	
	source.networkNumber = GET_PORTDESCRIPTOR(port)->ARouter.NetworkNumber;
	source.nodeNumber = GET_PORTDESCRIPTOR(port)->ARouter.NodeNumber;
	source.socketNumber = RtmpSocket;
	if ((sourceSocket = MapAddressToSocket(port, source)) < ATnoError) {
		//
	   // This may be okay during initialization... we're not started on all
	   //   ports yet.
		//
	
	   return;
	}
	
	//
	// Set up the static header to an RTMP data datagram (routers net/node
	//   number).  For half ports, since we really don't have such info fill in
	//   the address of the first non-half-port routing port.
	//
	
	if (GET_PORTDESCRIPTOR(port)->PortType isnt NonAppleTalkHalfPort)
	   sourceNode = GET_PORTDESCRIPTOR(port)->ARouter;
	else {
		for (index = 0; index < MaximumNumberOfPorts; index += 1)
		  if (GET_PORTDESCRIPTOR(index)->Flags & PD_ACTIVE and
			  GET_PORTDESCRIPTOR(index)->PortType isnt NonAppleTalkHalfPort and
			  GET_PORTDESCRIPTOR(index)->RouterRunning) {
			 sourceNode = GET_PORTDESCRIPTOR(index)->ARouter;
			 break;
		  }
	
		//
		// If we didn't find an address we like, just use the dummy one in
		//   our current port (no doubt net/node = 0).
		//
	
		if (index >= MaximumNumberOfPorts)
		  sourceNode = GET_PORTDESCRIPTOR(port)->ARouter;
	}
	
	// Get the first buffer descriptor.
	
	if ((datagram = NewBufferDescriptor(MaximumDdpDatagramSize)) is Empty) {
		ErrorLog("SendRoutingData", ISevError, __LINE__, port,
				IErrFullRtmpOutOfMemory, IMsgFullRtmpOutOfMemory,
				Insert0());
		return;
	}
	
	//
	// Walk through the routing table building a tuple for each network;
	//   we may have to send multiple packets...
	//
	
	for (hash = 0; hash < NumberOfRtmpHashBuckets; hash += 1)
	   for (routingTableEntry = routingTable[hash];
			routingTableEntry isnt empty;
			routingTableEntry = routingTableEntry->next) {
		  if (newPacket)
		  {
			 // Build the static part of an RtmpData packet.
	
			 MoveShortMachineToWire(datagram->data + RtmpSendersNetworkOffset,
									sourceNode.networkNumber);
			 datagram->data[RtmpSendersIdLengthOffset] = 8;   // Bits
			 datagram->data[RtmpSendersIdOffset] = sourceNode.nodeNumber;
	
		   //
			 // For non-extended networks we need the version stamp, for exteneded
			 //   networks, we need to include a initial network range tuple as
			 //   part of the header.
		   //
	
			 if (not GET_PORTDESCRIPTOR(port)->ExtendedNetwork) {
				datagram->data[RtmpSendersIdOffset + 1] = 0;
				datagram->data[RtmpSendersIdOffset + 2] = 0;
				datagram->data[RtmpSendersIdOffset + 3] = RtmpVersionNumber;
																   //
				staticHeaderSize = RtmpSendersIdOffset + 1 + 2 + 1;  // + nodeID +
																	 //   filler +
																	 //   version
																   //
			 }
			 else {
				datagram->data[RtmpRangeStartOffset] =
					  (char)((GET_PORTDESCRIPTOR(port)->ThisCableRange.
							  FirstNetworkNumber >> 8) & 0xFF);
				datagram->data[RtmpRangeStartOffset + 1] =
					  (char)(GET_PORTDESCRIPTOR(port)->ThisCableRange.
							 FirstNetworkNumber & 0xFF);
				datagram->data[RtmpRangeStartOffset + 2] = RtmpTupleWithRange;
				datagram->data[RtmpRangeEndOffset] =
					  (char)((GET_PORTDESCRIPTOR(port)->ThisCableRange.
							  LastNetworkNumber >> 8) & 0xFF);
				datagram->data[RtmpRangeEndOffset + 1] =
					  (char)(GET_PORTDESCRIPTOR(port)->ThisCableRange.
							 LastNetworkNumber & 0xFF);
				datagram->data[RtmpRangeEndOffset + 2] = RtmpVersionNumber;
															   //
				staticHeaderSize = RtmpRangeEndOffset + 2 + 1;   // + last net number +
																 //     version
															   //
				#if Verbose and (IamNot a WindowsNT)
				   printf("   RTMP Tuple: port %d; net \"%d:%d\"; distance %d.\n", port,
						  GET_PORTDESCRIPTOR(port)->ThisCableRange.FirstNetworkNumber,
						  GET_PORTDESCRIPTOR(port)->ThisCableRange.LastNetworkNumber,
						  0);
				#endif
			 }
	
		   //
			 // Start of a new packet full of tuples; set the packet index to
			 //   the start up the tuples.
		   //
	
			 index = staticHeaderSize;
			 newPacket = False;
		  }
	
		//
		  // Should the current tuple be omitted due to split horizon
		  //   processing?
		//
	
		  if (doSplitHorizon)
			 if (routingTableEntry->numberOfHops isnt 0)
				if (routingTableEntry->port is port)
				   continue;
	
		  // Okay, place the tuple in the packet...
	
		  datagram->data[index++] =
				(char)(routingTableEntry->networkRange.firstNetworkNumber >> 8);
		  datagram->data[index++] =
				(char)(routingTableEntry->networkRange.firstNetworkNumber & 0xFF);
	
		  // Do "notify nieghbor" if our current state is bad.
	
		  if (routingTableEntry->entryState is PrettyBad or
			  routingTableEntry->entryState is Bad)
			 datagram->data[index++] = RtmpNumberOfHopsMask;
		  else
			 datagram->data[index++] = (char)(routingTableEntry->numberOfHops &
											  RtmpNumberOfHopsMask);
	
		//
		  // Send an extended tuple if the network range isn't one or the target
		  //   port is an extended network.
		//
	
		  if (GET_PORTDESCRIPTOR(port)->ExtendedNetwork or
			  routingTableEntry->networkRange.firstNetworkNumber isnt
					 routingTableEntry->networkRange.lastNetworkNumber) {
			 datagram->data[index - 1] |= RtmpExtendedTupleMask;
			 datagram->data[index++] =
				(char)(routingTableEntry->networkRange.lastNetworkNumber >> 8);
			 datagram->data[index++] =
				(char)(routingTableEntry->networkRange.lastNetworkNumber & 0xFF);
			 datagram->data[index++] = RtmpVersionNumber;
		  }
	
		  #if Verbose and (IamNot a WindowsNT)
			 printf("   RTMP Tuple: port %d; net \"%d:%d\"; distance %d.\n", port,
					routingTableEntry->networkRange.firstNetworkNumber,
					routingTableEntry->networkRange.lastNetworkNumber,
					routingTableEntry->numberOfHops);
		  #endif
	
		  unsentTuples = True;
	
		  // Send a data packet if we're full...
	
		  if (index + RtmpExtendedTupleSize >= MaximumDdpDatagramSize) {
			 // Send the DDP packet...
	
			 if (DeliverDdp(sourceSocket, destination,
							DdpProtocolRtmpResponseOrData,
							datagram, index, Empty, Empty, 0) isnt ATnoError)
				ErrorLog("SendRoutingData", ISevError, __LINE__, port,
						 IErrFullRtmpBadDataSend, IMsgFullRtmpBadDataSend,
						 Insert0());
	
			 // Get the next buffer descriptor.
	
			 if ((datagram = NewBufferDescriptor(MaximumDdpDatagramSize)) is
				 Empty) {
				ErrorLog("SendRoutingData", ISevError, __LINE__, port,
						 IErrFullRtmpOutOfMemory, IMsgFullRtmpOutOfMemory,
						 Insert0());
				return;
			 }
	
			 newPacket = True;
			 unsentTuples = False;
		  }
	   }
	
	//
	// If "unsentTuples" is set then we have a partial packet that we need
	//   to send.
	//
	
	if (unsentTuples) {
		if (DeliverDdp(sourceSocket, destination,
					  DdpProtocolRtmpResponseOrData,
					  datagram, index, Empty, Empty, 0) isnt ATnoError)
		  ErrorLog("SendRoutingData", ISevError, __LINE__, port,
				   IErrFullRtmpBadDataSend, IMsgFullRtmpBadDataSend,
				   Insert0());
	}
	else
	   FreeBufferChain(datagram);   // Free unused buffer chain.
	
	// The deed is done.
	
	return;
	
}  // SendRoutingData




#if (Verbose and (IamNot a WindowsNT)) or (Iam a Primos)
void DumpRtmpRoutingTable(void)
{
	RoutingTableEntry routingTableEntry;
	int index;
	ZoneList zone;
	char *p;
	
	DeferTimerChecking();
	DeferIncomingPackets();
	
	// Dump the RTMP routing tables for debugging.
	
	printf("********** RTMP routing table start:\n");
	for (index = 0; index < NumberOfRtmpHashBuckets; index += 1)
	   for (routingTableEntry = routingTable[index];
			routingTableEntry isnt empty;
			routingTableEntry = routingTableEntry->next) {
		  printf("Range %05u:%05u; port %d; state %d; hops %02d; "
				 "next bridge %u.%u.\n",
				 routingTableEntry->networkRange.firstNetworkNumber,
				 routingTableEntry->networkRange.lastNetworkNumber,
				 routingTableEntry->port,
				 routingTableEntry->entryState,
				 routingTableEntry->numberOfHops,
				 routingTableEntry->nextRouter.networkNumber,
				 routingTableEntry->nextRouter.nodeNumber);
		  p = "     ZoneList = ";
		  for (zone = routingTableEntry->zoneList;
			   zone isnt empty;
			   zone = zone->next) {
			 printf("%s%s\n", p, zone->zone);
			 p = "                ";
		  }
	   }
	printf("********** RTMP routing table end.\n");
	
	HandleIncomingPackets();
	HandleDeferredTimerChecks();
	return;
	
}  // DumpRtmpRoutingTable
#endif

#endif