-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- NetWatcher.mesa, HGM, 11-Feb-84 16:41:36

DIRECTORY
  Ascii USING [CR],
  CmFile USING [Handle, TableError],
  Format USING [HostNumber, NetworkNumber],
  Heap USING [systemZone],
  Process USING [Detach, MsecToTicks, Ticks, Pause, Yield],
  Put USING [Text],
  Runtime USING [UnboundProcedure],
  String USING [AppendChar, AppendString, AppendNumber],
  StringLookUp USING [noMatch],
  System USING [
    GreenwichMeanTime, GetGreenwichMeanTime, HostNumber, NetworkNumber,
    nullHostNumber, nullNetworkNumber],
  Time USING [AppendCurrent],
  Token USING [Boolean],

  Indirect USING [Close, NextValue, OpenSection],
  InrFriends USING [DriverDetails, GetRouteInfo],
  Router USING [
    endEnumeration, EnumerateRoutingTable, GetDelayToNet, infinity,
    NoTableEntryForNet, startEnumeration];

NetWatcher: PROGRAM
  IMPORTS
    CmFile, Format, Heap, Process, Put, Runtime, String, System, Time, Token,
    Indirect, InrFriends, Router =
  BEGIN
  
  z: UNCOUNTED ZONE = Heap.systemZone;

  seconds: CARDINAL ← 60;
  oneSecond: Process.Ticks = Process.MsecToTicks[1000];

  first: Handle ← NIL;
  
  Handle: TYPE = LONG POINTER TO Object;
  Object: TYPE = RECORD [
    next: Handle,
    net: System.NetworkNumber,
    viaNet: System.NetworkNumber,
    viaHost: System.HostNumber,
    old: BOOLEAN,
    delay: CARDINAL ];
    
  Init: PROCEDURE =
    BEGIN
    off: BOOLEAN ← FALSE;
    cmFile: CmFile.Handle;
    Option: TYPE = {disable};
    NextValue: PROCEDURE [
      h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY Option OF LONG STRING]
      RETURNS [Option] = LOOPHOLE[Indirect.NextValue];
    optionTable: ARRAY Option OF LONG STRING ← [disable: "Disable"L];
    cmFile ← Indirect.OpenSection["NetWatcher"L];
    IF cmFile = NIL THEN RETURN;
    DO
      option: Option;
      text: STRING = [200];
      option ← NextValue[cmFile, DESCRIPTOR[optionTable] ! CmFile.TableError => RETRY];
      SELECT option FROM
        LOOPHOLE[StringLookUp.noMatch] => EXIT;
        disable =>
	  BEGIN
	  off ← Token.Boolean[cmFile];
	  END;
        ENDCASE => ERROR;
      ENDLOOP;
    Indirect.Close[cmFile];
    IF ~off THEN Process.Detach[FORK Watch[]];
    END;

  Watch: PROCEDURE =
    BEGIN
    time: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    Process.Pause[120*oneSecond];
    EnumerateRoutingTable[AddEntry];
    DO
      WHILE (System.GetGreenwichMeanTime[] - time) < seconds DO
        Process.Pause[oneSecond]; ENDLOOP;
      time ← System.GetGreenwichMeanTime[];
      MarkOldNets[];
      EnumerateRoutingTable[AddAndPrint];
      CheckOldNets[];
      THROUGH [0..100) DO Process.Yield[]; ENDLOOP;
      ENDLOOP;
    END;

  EnumerateRoutingTable: PROCEDURE [
    proc: PROCEDURE [
      net: System.NetworkNumber,
      delay: CARDINAL,
      viaNet: System.NetworkNumber,
      viaHost: System.HostNumber] ] =
    BEGIN
    net: System.NetworkNumber ← Router.startEnumeration;
    FOR delay: CARDINAL IN [0..Router.infinity) DO
      net: System.NetworkNumber ← Router.startEnumeration;
      DO
        details: InrFriends.DriverDetails;
        net ← Router.EnumerateRoutingTable[net, delay];
        IF net = Router.endEnumeration THEN EXIT;
        [, details] ← InrFriends.GetRouteInfo[net !
          Runtime.UnboundProcedure, Router.NoTableEntryForNet =>
	    BEGIN
	    details.driverNetwork ← System.nullNetworkNumber;
	    details.via.host ← System.nullHostNumber;
	    CONTINUE;
	    END];
        proc[net, delay, details.driverNetwork, details.via.host];
        ENDLOOP;
      ENDLOOP;
    END;

  AddEntry: PROCEDURE [
    net: System.NetworkNumber,
    delay: CARDINAL,
    viaNet: System.NetworkNumber,
    viaHost: System.HostNumber] =
    BEGIN
    handle: Handle;
    handle ← z.NEW[Object];
    handle↑ ← [
      next: first,
      net: net,
      viaNet: viaNet,
      viaHost: viaHost,
      old: FALSE,
      delay: delay ];
    first ← handle;
    END;

  AddAndPrint: PROCEDURE [
    net: System.NetworkNumber,
    delay: CARDINAL,
    viaNet: System.NetworkNumber,
    viaHost: System.HostNumber] =
    BEGIN
    FOR handle: Handle ← first, handle.next UNTIL handle = NIL DO
      IF handle.net = net THEN
        BEGIN handle.old ← FALSE; RETURN; END;
      ENDLOOP;
    AddEntry[net, delay, viaNet, viaHost];
    IF first.delay = Router.infinity THEN RETURN;  -- Routing fault: still unreachable
    PrintMessage[first];
    END;

  MarkOldNets: PROCEDURE =
    BEGIN
    FOR handle: Handle ← first, handle.next UNTIL handle = NIL DO
      handle.old ← TRUE;
      ENDLOOP;
    END;
  
  CheckOldNets: PROCEDURE =
    BEGIN
    handle: Handle ← first;
    UNTIL handle = NIL DO
      delay: CARDINAL ← Router.infinity;
      details: InrFriends.DriverDetails;
      IF handle.old THEN
        BEGIN
	temp: Handle ← handle;
	IF handle.delay # Router.infinity THEN
	  BEGIN
	  handle.delay ← Router.infinity;
	  PrintMessage[handle];
	  END;
	IF first = handle THEN
	  BEGIN
	  first ← handle.next;
	  END
	ELSE
	  BEGIN
	  FOR prev: Handle ← first, prev.next DO
	    IF prev.next = handle THEN
	      BEGIN
	      prev.next ← handle.next;
	      EXIT;
	      END;
	    ENDLOOP;
	  END;
	handle ← handle.next;
	z.FREE[@temp];
	LOOP;
	END;
      delay ← Router.GetDelayToNet[handle.net ! Router.NoTableEntryForNet => CONTINUE];
      [, details] ← InrFriends.GetRouteInfo[handle.net !
        Runtime.UnboundProcedure, Router.NoTableEntryForNet =>
	  BEGIN
	  details.driverNetwork ← System.nullNetworkNumber;
	  details.via.host ← System.nullHostNumber;
	  CONTINUE;
	  END];
      IF handle.delay # delay
        OR handle.viaNet # details.driverNetwork
        OR handle.viaHost # details.via.host THEN
	BEGIN
        handle.delay ← delay;
        handle.viaNet ← details.driverNetwork;
        handle.viaHost ← details.via.host;
        PrintMessage[handle];
	END;
      handle ← handle.next;
      ENDLOOP;
    END;
  
  PrintMessage: PROCEDURE [handle: Handle] =
    BEGIN
    text: STRING = [200];
    net: RECORD [a, b: WORD] = LOOPHOLE[handle.net];
    Time.AppendCurrent[text];
    String.AppendString[text, "  Net "L];
    AppendNetNumber[text, handle.net];
    IF net.b < 256 THEN
      BEGIN
      String.AppendString[text, " ("L];
      AppendNetNumberOctal[text, handle.net];
      String.AppendString[text, ")"L];
      END;
    String.AppendString[text, " is "L];
    SELECT TRUE FROM
      handle.delay = Router.infinity =>
        String.AppendString[text, "unreachable"L];
      (handle.delay = 0) => String.AppendString[text, "directly connected"L];
      ENDCASE =>
        BEGIN
        String.AppendNumber[text, handle.delay, 10];
        String.AppendString[text, " hops via "L];
	AppendNetNumber[text, handle.viaNet];
	String.AppendChar[text, '.];
	AppendHostNumber[text, handle.viaHost];
	String.AppendChar[text, '.];
        END;
    LogString[text];
    THROUGH [0..1000) DO Process.Yield[]; ENDLOOP;  -- Wait longer if things are busy
    Process.Pause[5*oneSecond];
    END;
  
  AppendNetNumber: PROCEDURE [string: LONG STRING, net: System.NetworkNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.NetworkNumber[Append, net, productSoftware];
    END;

  AppendNetNumberOctal: PROCEDURE [string: LONG STRING, net: System.NetworkNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.NetworkNumber[Append, net, octal];
    END;

  AppendHostNumber: PROCEDURE [string: LONG STRING, host: System.HostNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.HostNumber[Append, host, productSoftware];
    END;

  AppendNetAndHost: PROCEDURE [
    string: LONG STRING, net: System.NetworkNumber, host: System.HostNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.NetworkNumber[Append, net, productSoftware];
    String.AppendChar[string, '.];
    Format.HostNumber[Append, host, productSoftware];
    String.AppendChar[string, '.];
    END;

  LogString: PROCEDURE [text: LONG STRING] =
    BEGIN
    String.AppendChar[text, '.];
    String.AppendChar[text, Ascii.CR];
    Put.Text[NIL, text];
    END;


  Init[];
  END.