/*   FullRtmp.c,  /appletalk/source,  Garth Conboy,  01/04/90  */
/*   Copyright (c) 1988 by Pacer Software Inc., La Jolla, CA  */

/*   GC - Initial coding (from various other pieces: non-router AppleTalk
          phase II RtmpStub and router AppleTalk phase I implementation).
     GC - (08/18/90): New error logging mechanism.
     GC - (10/08/90): Half port support.
     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.

     *** Make the PVCS source control system happy:
     $Header$
     $Log$
     ***

     Handle the RTMP protocol for a routing node.

*/

#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 (portDescriptors[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 (portDescriptors[port].portType isnt NonAppleTalkHalfPort and
         portDescriptors[port].seenRouterRecently and
         portDescriptors[port].initialNetworkRange.firstNetworkNumber isnt
              UnknownNetworkNumber)
        if (portDescriptors[port].initialNetworkRange.firstNetworkNumber isnt
            portDescriptors[port].thisCableRange.firstNetworkNumber or
            portDescriptors[port].initialNetworkRange.lastNetworkNumber isnt
            portDescriptors[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 (portDescriptors[port].portType is NonAppleTalkHalfPort)
     {
        portDescriptors[port].thisCableRange.firstNetworkNumber =
              UnknownNetworkNumber;
        portDescriptors[port].thisCableRange.lastNetworkNumber =
              UnknownNetworkNumber;
     }
     else if (not portDescriptors[port].seedRouter)
     {
        ErrorLog("StartRtmpProcessingOnPort", ISevError, __LINE__, port,
                 IErrFullRtmpNoSeedCantStart, IMsgFullRtmpNoSeedCantStart,
                 Insert0());
        return(False);
     }
     else if (not portDescriptors[port].seenRouterRecently)
        portDescriptors[port].thisCableRange =
                   portDescriptors[port].initialNetworkRange;
  }
  else
  {
     /* For non-extended networks, we need to either find or seed our network
        number. */

     if (not GetOrSetNetworkNumber(port, (unsigned int)(
                                     portDescriptors[port].seedRouter ?
                                     portDescriptors[port].initialNetworkRange.
                                         firstNetworkNumber :
                                     UnknownNetworkNumber)))
        return(False);

     if (not portDescriptors[port].seenRouterRecently)
        portDescriptors[port].thisCableRange.firstNetworkNumber =
                 portDescriptors[port].initialNetworkRange.firstNetworkNumber;

     /* On non-extended networks, we would have "allocated" a node with network
        zero, fix this up here! */

     portDescriptors[port].activeNodes->extendedNode.networkNumber =
         portDescriptors[port].aRouter.networkNumber =
              routerNode.networkNumber =
                   portDescriptors[port].thisCableRange.firstNetworkNumber;
  }

  /* Well, we're a router now, so mark us as A-ROUTER. */

  portDescriptors[port].seenRouterRecently = True;
  portDescriptors[port].aRouter = routerNode;
  portDescriptors[port].routerRunning = True;

  /* Okay, make it so... */

  if (portDescriptors[port].portType isnt NonAppleTalkHalfPort)
     EnterIntoRoutingTable(portDescriptors[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 ((portDescriptors[port].extendedNetwork and
          datagramLength < RtmpDataMinimumSizeExtended) or
         (not portDescriptors[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(portDescriptors[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)((portDescriptors[port].aRouter.networkNumber >> 8) & 0xFF);
     buffer->data[RtmpSendersNetworkOffset + 1] =
          (char)(portDescriptors[port].aRouter.networkNumber & 0xFF);
     buffer->data[RtmpSendersIdLengthOffset] = 8;  /* Bits */
     buffer->data[RtmpSendersIdOffset] = portDescriptors[port].aRouter.nodeNumber;

     /* On an extended port, we also want to add the initial network range
        tuple. */

     if (not portDescriptors[port].extendedNetwork)
        responseSize = RtmpSendersIdOffset + sizeof(char);
     else
     {
        buffer->data[RtmpRangeStartOffset] =
              (char)((portDescriptors[port].thisCableRange.
                      firstNetworkNumber >> 8) & 0xFF);
        buffer->data[RtmpRangeStartOffset + 1] =
              (char)(portDescriptors[port].thisCableRange.
                     firstNetworkNumber & 0xFF);
        buffer->data[RtmpRangeStartOffset + 2] = RtmpTupleWithRange;
        buffer->data[RtmpRangeEndOffset] =
              (char)((portDescriptors[port].thisCableRange.
                      lastNetworkNumber >> 8) & 0xFF);
        buffer->data[RtmpRangeEndOffset + 1] =
              (char)(portDescriptors[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 (portDescriptors[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 (portDescriptors[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;
		   }

        /* 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 (portDescriptors[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 =
         portDescriptors[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 portDescriptors[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,
             &portDescriptors[port].seenRouterRecently);
  }

  /* If we got an answer, we're set... */

  if (portDescriptors[port].seenRouterRecently)
  {
     if (suggestedNetworkNumber isnt UnknownNetworkNumber and
         portDescriptors[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);
  }

  portDescriptors[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 (portDescriptors[port].portActive and
         portDescriptors[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 = portDescriptors[port].aRouter.networkNumber;
  source.nodeNumber = portDescriptors[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 (portDescriptors[port].portType isnt NonAppleTalkHalfPort)
     sourceNode = portDescriptors[port].aRouter;
  else
  {
     for (index = 0; index < MaximumNumberOfPorts; index += 1)
        if (portDescriptors[index].portActive and
            portDescriptors[index].portType isnt NonAppleTalkHalfPort and
            portDescriptors[index].routerRunning)
        {
           sourceNode = portDescriptors[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 = portDescriptors[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 portDescriptors[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)((portDescriptors[port].thisCableRange.
                            firstNetworkNumber >> 8) & 0xFF);
              datagram->data[RtmpRangeStartOffset + 1] =
                    (char)(portDescriptors[port].thisCableRange.
                           firstNetworkNumber & 0xFF);
              datagram->data[RtmpRangeStartOffset + 2] = RtmpTupleWithRange;
              datagram->data[RtmpRangeEndOffset] =
                    (char)((portDescriptors[port].thisCableRange.
                            lastNetworkNumber >> 8) & 0xFF);
              datagram->data[RtmpRangeEndOffset + 1] =
                    (char)(portDescriptors[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,
                        portDescriptors[port].thisCableRange.firstNetworkNumber,
                        portDescriptors[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 (portDescriptors[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
