-- File: ArpaRIPImpl.mesa - last edit:
-- AOF                  1-Mar-88 11:35:23
-- JAV                 12-Nov-87 15:22:47

-- Copyright (C) 1987, 1988 by Xerox Corporation. All rights reserved.


DIRECTORY
  ArpaBuffer USING [Body, Buffer, ReturnBuffer],
  ArpaPort USING [Create, GetPacket, GetSendBuffer, Handle, maxIPDataBytes, 
    PutPacket, SetIPLengths, SetUDPLength, Timeout, UDPHeaderBytes],
  ArpaPortInternal USING [BuildMasks, GetMyBroadcastAddr, GetSubnetMask],
  ArpaRIP USING [],
  ArpaRouter USING [InternetAddress, Port, unknownInternetAddress],
  ArpaRoutingTable USING [defaultRth, NetworkContext],
  ArpaSysParameters USING [GetTypeOfService],
  ArpaTypes USING [Cardinal32],
  Environment USING [bytesPerWord],
  Inline USING [LowHalf],
  Mopcodes USING [zEXCH],
  Process USING [Detach, EnableAborts, MsecToTicks, Pause, SecondsToTicks, 
    SetTimeout],
  System USING [GetClockPulses];

ArpaRIPImpl: MONITOR
  IMPORTS
    ArpaBuffer, ArpaPort, ArpaPortInternal, ArpaRouter, ArpaRoutingTable, 
    ArpaSysParameters, Inline, Process, System
  EXPORTS ArpaRIP =
  BEGIN
  
  RIPPort: ArpaRouter.Port = LOOPHOLE[520];
  
  bpw: NATURAL = Environment.bytesPerWord;
  pleaseStop: BOOLEAN ← FALSE;
  
  RoutingInfoTuple: TYPE = MACHINE DEPENDENT RECORD [
    family(0: 0..15): CARDINAL ← 0,
    port(1: 0..15): CARDINAL ← 0,
    address(2: 0..31): ArpaRouter.InternetAddress,
    pad1(4: 0..31): ArpaTypes.Cardinal32 ← [0, 0],
    pad2(6: 0..31): ArpaTypes.Cardinal32 ← [0, 0],
    delay(8: 0..31): ArpaTypes.Cardinal32 ← [0, 0]
    ];
    
  RoutingInfoType: TYPE = MACHINE DEPENDENT {
    routingInfoRequest(1), routingInfoResponse, (255)};
  
  RoutingVersion: TYPE = MACHINE DEPENDENT {vers1(1), (255)};
    
  RoutingInfoPacket: TYPE = MACHINE DEPENDENT RECORD [
    routingType (0: 0..7): RoutingInfoType,
    routingVersions (0: 8..15): RoutingVersion,
    routingTuple(1): ARRAY INTEGER[0..0) OF RoutingInfoTuple
    ];
    
  routingTupleSize: CARDINAL = SIZE[RoutingInfoTuple];
  routingPcktsPerBuffer: CARDINAL =
    (ArpaPort.maxIPDataBytes - ArpaPort.UDPHeaderBytes - bpw) /
    (routingTupleSize * bpw);

  TCPIPFamily: CARDINAL = 2;
  
  routerTimer, auxRouterTimer: CONDITION;
  
  udpHandle: ArpaPort.Handle ← NIL;
  
  StringProc: TYPE = PROCEDURE [s: LONG STRING, clientData: LONG POINTER ← NIL];
  
  outputProc: StringProc ← NIL;
  
  CardToDelay: PROC[LONG CARDINAL] RETURNS[ArpaTypes.Cardinal32] =
    MACHINE CODE {Mopcodes.zEXCH};
  
  DelayToCard: PROC[ArpaTypes.Cardinal32] RETURNS[LONG CARDINAL] =
    MACHINE CODE {Mopcodes.zEXCH};
	  
  StartRouter: PUBLIC PROCEDURE [proc: PROCEDURE [s: LONG STRING, clientData: LONG POINTER ← NIL]] =
    BEGIN
    udpHandle ← ArpaPort.Create[RIPPort, 1, 40, normal];
    outputProc ← proc;
    Process.Detach[FORK Router];
    Process.Pause[Process.MsecToTicks[1000]];
    Process.Detach[FORK RouterServer[]];
    END;
    
  StopRouter: PUBLIC PROCEDURE =
    BEGIN
      pleaseStop ← TRUE;
    END;
    
  Router: PROCEDURE =
    BEGIN
    b: ArpaBuffer.Buffer;
    body: ArpaBuffer.Body;
    tuple: RoutingInfoTuple;
    ripPacket: LONG POINTER TO RoutingInfoPacket;
    hopsToFillTo: CARDINAL ← LAST[CARDINAL]; 
    WHILE ~pleaseStop DO
      body ← (b ← ArpaPort.GetPacket[udpHandle ! ArpaPort.Timeout => IF pleaseStop THEN EXIT ELSE RETRY]).arpa;
      IF body.ipHeader.protocol # userDatagram THEN {
	ArpaBuffer.ReturnBuffer[b]; LOOP};
      ripPacket ← LOOPHOLE[@body.user.bytes];
      SELECT ripPacket.routingType FROM
	routingInfoRequest => {
	  netNumber: ArpaRouter.InternetAddress ← ripPacket.routingTuple[0].address;
	  context: ArpaRoutingTable.NetworkContext ← NARROW[b.fo.context];
	    ArpaBuffer.ReturnBuffer[b];
	    SendRoutingInfo[netNumber, context, udpHandle, FALSE]};
	routingInfoResponse => {
	  tuples: NATURAL = ((body.user.length - ArpaPort.UDPHeaderBytes) - 2) / (SIZE[RoutingInfoTuple] * bpw);
	  FOR i: CARDINAL IN[0..tuples) DO
	    tuple ← ripPacket.routingTuple[i];
	    IF DelayToCard[tuple.delay] <= hopsToFillTo THEN {
	      ArpaRoutingTable.defaultRth.addRoute[
		dest: tuple.address, 
		mask: 
		  IF ArpaPortInternal.GetSubnetMask[] # ArpaRouter.unknownInternetAddress THEN 
		    ArpaPortInternal.GetSubnetMask[]
		  ELSE 
		    ArpaPortInternal.BuildMasks[tuple.address].netMask,
		route: LOOPHOLE[body.ipHeader.source],
		delay: Inline.LowHalf[DelayToCard[tuple.delay]],
		context: NARROW[b.fo.context]]};	-- context is provided so that if the old context # new context then routes can be compared
	  ENDLOOP;
	  ArpaBuffer.ReturnBuffer[b];
	  };
      ENDCASE => {ArpaBuffer.ReturnBuffer[b]; LOOP};
    ENDLOOP;
  END; 
  
  SendRoutingInfo: PROCEDURE [netNumber: ArpaRouter.InternetAddress, context: ArpaRoutingTable.NetworkContext, portHandle: ArpaPort.Handle, flash: BOOLEAN] =
  BEGIN
  
  b: ArpaBuffer.Buffer ← NIL;
  body: ArpaBuffer.Body;
  tuples: CARDINAL ← 0;
  first: BOOLEAN ← TRUE;
  myBroadcastAddr: ArpaRouter.InternetAddress ← ArpaPortInternal.GetMyBroadcastAddr[];
  ripPacket: LONG POINTER TO RoutingInfoPacket;
    IF netNumber = ArpaRouter.unknownInternetAddress THEN {	-- sending to all nets (gratuitious info)
      FOR delay: CARDINAL IN [0..16) DO 
	net: ArpaRouter.InternetAddress ← ArpaRoutingTable.defaultRth.startEnumeration;
	UNTIL net = ArpaRoutingTable.defaultRth.endEnumeration DO
	  IF first THEN {
	    first ← FALSE;
	    b ← ArpaPort.GetSendBuffer[portHandle];
	    b.fo.allNets ← TRUE;	-- sending to all contected devices.
	    body ← b.arpa;
	    ripPacket ← LOOPHOLE[@body.user.bytes];
	    body.ipHeader.lifetime ← 60;
	    body.ipHeader.protocol ← userDatagram;
	    body.ipHeader.service ← ArpaSysParameters.GetTypeOfService[];
	    body.ipHeader.identification ← Inline.LowHalf[System.GetClockPulses[]];
	    body.ipHeader.destination ← LOOPHOLE[myBroadcastAddr];
	    --udp fields.
	    body.user.sourcePort ← LOOPHOLE[RIPPort];
	    body.user.destinationPort ← LOOPHOLE[RIPPort];
	    ripPacket.routingVersions ← vers1;
	    ripPacket.routingType ← routingInfoResponse;
	    };
	  net ← ArpaRoutingTable.defaultRth.enumerate[net, delay, flash];
	  IF net = ArpaRoutingTable.defaultRth.endEnumeration THEN LOOP;
	  ripPacket.routingTuple[tuples].family ← TCPIPFamily;
	  ripPacket.routingTuple[tuples].port ← 0;
	  ripPacket.routingTuple[tuples].address ← net;
	  ripPacket.routingTuple[tuples].pad1 ← [0, 0];
	  ripPacket.routingTuple[tuples].pad2 ← [0, 0];
	  ripPacket.routingTuple[tuples].delay ← CardToDelay[delay + 1];
	  tuples ← tuples + 1;
	  IF tuples >= routingPcktsPerBuffer THEN {
	    ArpaPort.SetUDPLength[body, (tuples * routingTupleSize + 1) * bpw];
	    ArpaPort.SetIPLengths[
	      body, 0, (tuples * routingTupleSize + 1) * bpw + ArpaPort.UDPHeaderBytes];
	    ArpaPort.PutPacket[portHandle, b];
	    first ← TRUE;
	    tuples ← 0};
	ENDLOOP
      ENDLOOP;
      IF tuples > 0 THEN {
	ArpaPort.SetUDPLength[body, (tuples * routingTupleSize + 1) * bpw];
	ArpaPort.SetIPLengths[
	  body, 0, (tuples * routingTupleSize + 1) * bpw + ArpaPort.UDPHeaderBytes];
	ArpaPort.PutPacket[portHandle, b];
	outputProc["P"]}
      ELSE ArpaBuffer.ReturnBuffer[b]}
    ELSE { -- only info for one net requested.
      b ← ArpaPort.GetSendBuffer[portHandle];
      body ← b.arpa;
      ripPacket ← LOOPHOLE[@body.user];
      body.ipHeader.lifetime ← 60;
      body.ipHeader.protocol ← userDatagram;
      body.ipHeader.service ← ArpaSysParameters.GetTypeOfService[];
      body.ipHeader.identification ← Inline.LowHalf[System.GetClockPulses[]];
      body.ipHeader.destination ← LOOPHOLE[myBroadcastAddr];
      --udp fields.
      ripPacket.routingVersions ← vers1;
      ripPacket.routingType ← routingInfoResponse;
      body.user.destinationPort ← LOOPHOLE[RIPPort];
      -- tuple
      ripPacket.routingTuple[0].family ← TCPIPFamily;
      ripPacket.routingTuple[0].port ← 0;
      ripPacket.routingTuple[0].address ← netNumber;
      ripPacket.routingTuple[0].delay ← CardToDelay[ArpaRoutingTable.defaultRth.getDelay[netNumber]];
      
      ArpaPort.SetUDPLength[body, routingTupleSize * bpw];
      ArpaPort.SetIPLengths[body, 0, routingTupleSize * bpw + ArpaPort.UDPHeaderBytes];
      ArpaPort.PutPacket[portHandle, b];
      outputProc["P"];};
  END;
  
  RouterServer: PROCEDURE =
    BEGIN
    flash: BOOLEAN ← FALSE;
    
    Wait: ENTRY PROCEDURE [condition: CONDITION] = INLINE {
      ENABLE UNWIND => NULL;
      WAIT condition;
      }; --end of Wait
    
    Process.Pause[Process.MsecToTicks[100]];
    UNTIL pleaseStop DO
      ENABLE ABORTED => EXIT;
      Wait[routerTimer];
      IF pleaseStop THEN EXIT;
      -- wait a random amount of time [0..5] seconds before sending out the
      -- gratuitous response packet.  This will help prevent all inrs 
      -- from sending at the same time.
      Process.SetTimeout[@auxRouterTimer,
	Process.SecondsToTicks[Inline.LowHalf[System.GetClockPulses[]] MOD 5]];
      Wait[auxRouterTimer];
      IF pleaseStop THEN EXIT;
      outputProc[IF flash THEN "F" ELSE "T"];
      SendRoutingInfo[ArpaRouter.unknownInternetAddress, NIL, udpHandle, flash];
      flash ← ~flash;
      ENDLOOP;
    END;  --RouterServer
    

  Process.SetTimeout[@routerTimer, Process.MsecToTicks[13500]];
  -- set for 13.5 seconds so that when the extra random wait [0..5] seconds
  -- is added, the average time between gratuitous pkts is 30 seconds
  Process.SetTimeout[@auxRouterTimer, Process.MsecToTicks[500]];
  Process.EnableAborts [@routerTimer];
  Process.EnableAborts [@auxRouterTimer];

END.