-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- OthelloNS.mesa, HGM, 19-Apr-85 16:19:48

DIRECTORY
  Format USING [HostNumber, NetworkAddress, NetworkNumber, NetFormat, StringProc],
  OthelloDefs USING [
    AbortingCommand, CheckUserAbort, CommandProcessor, IndexTooLarge, MyNameIs,
    NewLine, ReadChar, RegisterCommandProc, WriteChar, WriteLine, WriteLongNumber, WriteString],
  OthelloForgot USING [GetNameWithSpaces],
  Process USING [Detach, Yield],
  Runtime USING [UnboundProcedure],
  String USING [AppendChar, AppendNumber, AppendString],
  System USING [
    HostNumber, localHostNumber, NetworkAddress, NetworkNumber,
    nullHostNumber, nullNetworkNumber, nullSocketNumber, SocketNumber],
  Unformat USING [Error, NetworkAddress],

  AddressTranslation USING [Error, PrintError, StringToNetworkAddress],
  Buffer USING [NSBuffer],
  Driver USING [GetDeviceChain, Network],
  EthernetDriverFriends USING [EtherStatsInfo],
  InrFriends USING [DriverDetails, GetRouteInfo],
  NSConstants USING [echoerSocket],
  NSTypes USING [maxIDPDataWords],
  PhoneNetFriends USING [PhoneNetInfo],
  PhoneNetExtras USING [nsCongestion, nsTooGreedy, pupCongestion, pupTooGreedy],
  Router USING [
    endEnumeration, EnumerateRoutingTable, infinity, NoTableEntryForNet, startEnumeration],
  Socket USING [
    AssignNetworkAddress, ChannelHandle, Create, Delete,
    GetPacket, GetPacketBytes, GetSendBuffer, PutPacket,
    ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime, TimeOut];

OthelloNS: PROGRAM
  IMPORTS
    Format, OthelloDefs, OthelloForgot, Process, Runtime, String, System, Unformat,
    AddressTranslation, Driver, InrFriends, Router, Socket =
  BEGIN

  EchoUser: PROCEDURE =
    BEGIN
    funny, late, missed: LONG CARDINAL ← 0;
    recv, sent: LONG CARDINAL ← 0;
    wrong: LONG CARDINAL ← 0;
    me, him: System.NetworkAddress;
    soc: Socket.ChannelHandle;
    packetNumber: CARDINAL ← 0;
    b: Buffer.NSBuffer;
    pktBody: LONG POINTER TO ARRAY [0..0) OF WORD;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    OthelloForgot.GetNameWithSpaces["Echo to: "L, @echoName];
    him ← GetAddress[echoName, NSConstants.echoerSocket !
      Trouble => OthelloDefs.AbortingCommand[reason]];
    me ← Socket.AssignNetworkAddress[];
    soc ← Socket.Create[me];
    Socket.SetWaitTime[soc, 5000];
    WriteAddresses[me];
    OthelloDefs.WriteString[" => "L];
    WriteAddresses[him];
    OthelloDefs.NewLine[];
    Process.Detach[FORK Watch[]];
    UNTIL pleaseStop DO
      cycle: CARDINAL;
      IF (((packetNumber ← packetNumber + 1) MOD 5) = 0) THEN Process.Yield[];
      cycle ← packetNumber MOD NSTypes.maxIDPDataWords;
      IF cycle = 0 THEN OthelloDefs.WriteLine[""L];
      b ← Socket.GetSendBuffer[soc];
      Socket.SetDestination[b, him];
      Socket.SetPacketWords[b, cycle + 1];
      b.ns.packetType ← echo;
      b.ns.echoType ← echoRequest;
      pktBody ← @b.ns.nsWords;
      FOR k: CARDINAL IN [0..cycle) DO
        pktBody[k + 1] ← (k*400B + packetNumber); ENDLOOP;
      Socket.PutPacket[soc, b];
      sent ← sent + 1;
      -- now receive the echo or any back logged echos
      DO
        b ← Socket.GetPacket[soc !
          Socket.TimeOut =>
            BEGIN missed ← missed + 1; OthelloDefs.WriteChar['?]; EXIT; END];
        pktBody ← @b.ns.nsWords;
        SELECT TRUE FROM
          (Socket.GetPacketBytes[b] # 2*(cycle + 1))
            OR (b.ns.echoType # echoResponse)
            OR ((cycle # 0) AND (pktBody[0 + 1] # (0*400B + packetNumber))) =>
            BEGIN  -- probably a late packet, but could be trash, or error
            late ← late + 1;
            OthelloDefs.WriteChar['#];
            Socket.ReturnBuffer[b];
            LOOP;
            END;
          ENDCASE =>
            BEGIN  -- the echo we were looking for
            FOR k: CARDINAL IN [0..cycle) DO
              IF pktBody[k + 1] # (k*400B + packetNumber) THEN
                BEGIN wrong ← wrong + 1; OthelloDefs.WriteChar['~]; GOTO Wrong; END;
	      ENDLOOP;
            OthelloDefs.WriteChar['!];
            recv ← recv + 1;
            Socket.ReturnBuffer[b];
            EXIT;
	    EXITS Wrong => EXIT;
            END;
        ENDLOOP;
      ENDLOOP;
    Socket.Delete[soc];
    OthelloDefs.NewLine[];
    OthelloDefs.WriteString["Out: "L];
    OthelloDefs.WriteLongNumber[sent];
    OthelloDefs.WriteString[", In: "L];
    OthelloDefs.WriteLongNumber[recv];
    OthelloDefs.WriteString[" ("L];
    OthelloDefs.WriteLongNumber[(recv*100)/sent];
    OthelloDefs.WriteLine["%)"L];
    IF late # 0 THEN
      BEGIN
      OthelloDefs.WriteString["Late: "L];
      OthelloDefs.WriteLongNumber[late];
      OthelloDefs.WriteString[" ("L];
      OthelloDefs.WriteLongNumber[(late*100)/sent];
      OthelloDefs.WriteLine["%)"L];
      END;
    IF missed # 0 THEN
      BEGIN
      OthelloDefs.WriteString["Missing: "L];
      OthelloDefs.WriteLongNumber[missed];
      OthelloDefs.WriteString[" ("L];
      OthelloDefs.WriteLongNumber[(missed*100)/sent];
      OthelloDefs.WriteLine["%)"L];
      END;
    IF funny # 0 THEN
      BEGIN
      OthelloDefs.WriteLongNumber[funny];
      OthelloDefs.WriteLine[" funny"L];
      END;
    IF wrong # 0 THEN
      BEGIN
      OthelloDefs.WriteLongNumber[wrong];
      OthelloDefs.WriteLine[" wrong data"L];
      END;
    END;


  PrintRoutingTable: PROCEDURE =
    BEGIN
    PrintOne: PROCEDURE [net: System.NetworkNumber, delay: CARDINAL] =
      BEGIN
      hickup: BOOLEAN ← FALSE;
      delay2: CARDINAL;
      details: InrFriends.DriverDetails;
      [delay2, details] ← InrFriends.GetRouteInfo[net !
        Runtime.UnboundProcedure, Router.NoTableEntryForNet =>
	  BEGIN hickup ← TRUE; CONTINUE; END];
      IF hickup THEN duds ← duds + 1 ELSE nets ← nets + 1;
      IF k = 0 THEN OthelloDefs.WriteChar['|];
      WriteProductNetNumber[net];
      WriteNetNumber[net];
      OthelloDefs.WriteChar[' ];
      SELECT TRUE FROM
        hickup => OthelloDefs.WriteString["          ?????          "];
        ENDCASE => WriteNetAndHost[details.driverNetwork, details.via.host];
      D4[delay];
      IF delay # delay2 THEN OthelloDefs.WriteChar['?] ELSE OthelloDefs.WriteChar[' ];
      OthelloDefs.WriteString["|"L];
      IF (k ← k + 1) = 2 THEN BEGIN OthelloDefs.NewLine[]; k ← 0; Process.Yield[]; END;
      END;
    k, nets, duds: CARDINAL ← 0;
    OthelloDefs.NewLine[];
    OthelloDefs.WriteLine["  Local NS Routing Table."L];
    OthelloDefs.WriteString["|        Net                Via         Dly "L];
    OthelloDefs.WriteLine[  "|        Net                Via         Dly |"L];
    OthelloDefs.WriteString["|-------------------------------------------"L];
    OthelloDefs.WriteLine[  "|-------------------------------------------|"L];
    BEGIN
    PrintOne[System.nullNetworkNumber, 0];  -- Enumeration skips net 0
    FOR delay: CARDINAL IN [0..Router.infinity) DO
      net: System.NetworkNumber ← Router.startEnumeration;
      DO
        net ← Router.EnumerateRoutingTable[net, delay];
        IF net = Router.endEnumeration THEN EXIT;
        PrintOne[net, delay];
        OthelloDefs.CheckUserAbort[! ABORTED => GOTO Exit];
        ENDLOOP;
      REPEAT Exit => NULL;
      ENDLOOP;
    END;
    IF k # 0 THEN OthelloDefs.NewLine[];
    IF nets > 1 THEN
      BEGIN
      OthelloDefs.WriteString["There are "L];
      WriteDecimal[nets - 1];
      OthelloDefs.WriteLine[" active networks."L];
      END;
    IF duds > 0 THEN
      BEGIN
      OthelloDefs.WriteString["There are "L];
      WriteDecimal[duds];
      OthelloDefs.WriteLine[" dead slots."L];
      END;
    END;
    
  PrintDeviceStats: PROCEDURE =
    BEGIN
    firstNetwork: Driver.Network ← Driver.GetDeviceChain[];
    FOR network: Driver.Network ← firstNetwork, network.next UNTIL network = NIL DO
      OthelloDefs.CheckUserAbort[ ! ABORTED => EXIT];
      SELECT network.device FROM
        ethernet, ethernetOne =>
	  BEGIN
          stats: LONG POINTER TO EthernetDriverFriends.EtherStatsInfo ← network.statsLevel0;
	  OthelloDefs.WriteString["Ethernet"L];
	  IF network.device = ethernetOne THEN OthelloDefs.WriteString["One"L];
	  OthelloDefs.WriteString[" Statistics for Pup "L];
	  WriteNumber[network.pupNetNumber, 8, 0];
	  OthelloDefs.WriteString["#"L];
	  WriteNumber[network.pupHostNumber, 8, 0];
	  OthelloDefs.WriteString["#, NS net "L];
	  WriteNetNumbers[network.netNumber];
	  OthelloDefs.WriteLine["."L];
	  IF stats = NIL THEN
	    BEGIN
	    OthelloDefs.WriteLine["*** NO DRIVER STATS ***"L];
	    LOOP;
	    END;
	  OthelloDefs.WriteString["  Rcv: pkts "L];
	  OthelloDefs.WriteLongNumber[stats.packetsRecv];
	  OthelloDefs.WriteString[", words "L];
	  OthelloDefs.WriteLongNumber[stats.wordsRecv];
	  OthelloDefs.WriteString[", bad "L];
	  OthelloDefs.WriteLongNumber[stats.badRecvStatus];
	  OthelloDefs.WriteString[", missed "L];
	  OthelloDefs.WriteLongNumber[stats.packetsMissed];
	  IF stats.idleInput # 0 THEN
            BEGIN
            OthelloDefs.WriteString[", idle "L];
            OthelloDefs.WriteLongNumber[stats.idleInput];
            END;
	  OthelloDefs.NewLine[];
	  IF stats.badRecvStatus # 0 OR stats.okButDribble # 0 THEN
            BEGIN
            OthelloDefs.WriteString["    crc "L];
            OthelloDefs.WriteLongNumber[stats.badCrc];
            OthelloDefs.WriteString[", bad alignment but ok crc "L];
            OthelloDefs.WriteLongNumber[stats.badAlignmentButOkCrc];
            OthelloDefs.WriteString[", crc and bad alignment "L];
            OthelloDefs.WriteLongNumber[stats.crcAndBadAlignment];
            OthelloDefs.NewLine[];
            OthelloDefs.WriteString["    ok but dribble "L];
            OthelloDefs.WriteLongNumber[stats.okButDribble];
            OthelloDefs.WriteString[", too long "L];
            OthelloDefs.WriteLongNumber[stats.packetTooLong];
            OthelloDefs.WriteString[", overrun "L];
            OthelloDefs.WriteLongNumber[stats.overrun];
            OthelloDefs.NewLine[];
            END;
	  OthelloDefs.WriteString["  Xmit: pkts "L];
	  OthelloDefs.WriteLongNumber[stats.packetsSent];
	  OthelloDefs.WriteString[", words "L];
	  OthelloDefs.WriteLongNumber[stats.wordsSent];
	  OthelloDefs.WriteString[", bad "L];
	  OthelloDefs.WriteLongNumber[stats.badSendStatus];
	  OthelloDefs.NewLine[];
	  IF stats.stuckOutput # 0 OR stats.badSendStatus # 0
	    OR stats.tooManyCollisions # 0 THEN
            BEGIN
            OthelloDefs.WriteString["    underrun "L];
            OthelloDefs.WriteLongNumber[stats.underrun];
            OthelloDefs.WriteString[", stuck "L];
            OthelloDefs.WriteLongNumber[stats.stuckOutput];
            OthelloDefs.WriteString[", too many collisions "L];
            OthelloDefs.WriteLongNumber[stats.tooManyCollisions];
            OthelloDefs.NewLine[];
            END;
	  OthelloDefs.WriteString["  Lds:"L];
	  FOR i: CARDINAL IN [0..16) DO
            OthelloDefs.WriteChar[' ]; OthelloDefs.WriteLongNumber[stats.loadTable[i]]; ENDLOOP;
	  OthelloDefs.NewLine[];
          END;
	phonenet =>
	  BEGIN
          CrapForINRLoophole: TYPE = MONITORED RECORD [
            clientData: LONG UNSPECIFIED,
            lineNumber: CARDINAL];
	  foo: LONG POINTER TO CrapForINRLoophole = network.statsLevel0;
          stats: LONG POINTER TO PhoneNetFriends.PhoneNetInfo ← foo.clientData;
          OthelloDefs.WriteString["PhoneNet Statistics for Pup "L];
	  WriteNumber[network.pupNetNumber, 8, 0];
	  OthelloDefs.WriteString["#"L];
	  WriteNumber[network.pupHostNumber, 8, 0];
	  OthelloDefs.WriteString["#, NS net "L];
	  WriteNetNumbers[network.netNumber];
	  OthelloDefs.WriteString[", Line "L];
	  OthelloDefs.WriteLongNumber[stats.lineNumber];
	  OthelloDefs.WriteString[", "L];
	  OthelloDefs.WriteLongNumber[stats.speed];
	  OthelloDefs.WriteString["KB"L];
	  SELECT TRUE FROM
	    stats.remoteHostNumber = System.nullHostNumber =>
	      OthelloDefs.WriteString[",  Down"L];
	    stats.remoteHostNumber = System.localHostNumber =>
	      OthelloDefs.WriteString[",  Looped"L];
	    ENDCASE =>
	      BEGIN
	      OthelloDefs.NewLine[];
	      OthelloDefs.WriteString["  Up to "L];
	      WriteHostNumbers[stats.remoteHostNumber];
	      END;
	  OthelloDefs.NewLine[];
	  IF stats = NIL THEN RETURN;
	  OthelloDefs.WriteString["  Recv: pkts "L];
	  OthelloDefs.WriteLongNumber[stats.stats[pktsReceived]];
	  OthelloDefs.WriteString[", bytes "L];
	  OthelloDefs.WriteLongNumber[stats.stats[bytesReceived]];
	  OthelloDefs.WriteString[", rejected "L];
	  OthelloDefs.WriteLongNumber[stats.stats[pktsRejected]];
	  OthelloDefs.WriteString[", missed "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvErrorNoGet]];
	  OthelloDefs.WriteString[", idle "L];
	  OthelloDefs.WriteLongNumber[stats.stats[tooLongSinceLastReceive]];
	  OthelloDefs.NewLine[];
	  OthelloDefs.WriteString["    crc "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvErrorCRC]];
	  OthelloDefs.WriteString[", data lost "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvErrorDataLost]];
	  OthelloDefs.WriteString[", device error "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvDeviceError]];
	  OthelloDefs.WriteString[", timeout "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvErrorFrameTimeout]];
	  OthelloDefs.WriteString[", other error "L];
	  OthelloDefs.WriteLongNumber[stats.stats[rcvErrorUnknown]];
	  OthelloDefs.NewLine[];
	  OthelloDefs.WriteString["  Send: pkts "L];
	  OthelloDefs.WriteLongNumber[stats.stats[pktsSent]];
	  OthelloDefs.WriteString[", bytes "L];
	  OthelloDefs.WriteLongNumber[stats.stats[bytesSent]];
	  OthelloDefs.WriteString[", Pups "L];
	  OthelloDefs.WriteLongNumber[stats.stats[pupSent]];
	  OthelloDefs.WriteString[", NS "L];
	  OthelloDefs.WriteLongNumber[stats.stats[nsSent]];
	  OthelloDefs.WriteString[", bad "L];
	  OthelloDefs.WriteLongNumber[stats.stats[sendErrorBadStatus]];
	  OthelloDefs.WriteString[", stuck "L];
	  OthelloDefs.WriteLongNumber[stats.stats[queueTooOld]];
	  OthelloDefs.NewLine[];
	  OthelloDefs.WriteString["    queue too long "L];
	  OthelloDefs.WriteLongNumber[stats.stats[congestion]];
	  OthelloDefs.WriteString[" Pup "L];
	  OthelloDefs.WriteLongNumber[stats.stats[PhoneNetExtras.pupCongestion]];
	  OthelloDefs.WriteString[", NS "L];
	  OthelloDefs.WriteLongNumber[stats.stats[PhoneNetExtras.nsCongestion]];
	  OthelloDefs.NewLine[];
	  OthelloDefs.WriteString["    conn too greedy "L];
	  OthelloDefs.WriteLongNumber[stats.stats[connTooGreedy]];
	  OthelloDefs.WriteString[", Pup "L];
	  OthelloDefs.WriteLongNumber[stats.stats[PhoneNetExtras.pupTooGreedy]];
	  OthelloDefs.WriteString[", NS "L];
	  OthelloDefs.WriteLongNumber[stats.stats[PhoneNetExtras.nsTooGreedy]];
	  OthelloDefs.NewLine[];
          IF stats.stats[controlPktReceived] # 0 THEN {
	    OthelloDefs.WriteString["    probes/duplicates discarded: "L];
	    OthelloDefs.WriteLongNumber[stats.stats[controlPktReceived]];
	    OthelloDefs.NewLine[]; };
	  END;
        ENDCASE => NULL;
      ENDLOOP;
    END;

  WriteAddresses: PROCEDURE [a: System.NetworkAddress] =
    BEGIN
    Push: Format.StringProc = BEGIN OthelloDefs.WriteString[s]; END;
    Format.NetworkAddress[Push, a, productSoftware];
    OthelloDefs.WriteString["="L];
    Format.NetworkAddress[Push, a, octal];
    END;

  WriteHostNumbers: PROCEDURE [net: System.HostNumber] =
    BEGIN
    Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN OthelloDefs.WriteString[s]; END;
    Format.HostNumber[Push, net, productSoftware];
    OthelloDefs.WriteString["="L];
    Format.HostNumber[Push, net, octal];
    END;
      
  WriteNetNumbers: PROCEDURE [net: System.NetworkNumber] =
    BEGIN
    Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN OthelloDefs.WriteString[s]; END;
    Format.NetworkNumber[Push, net, productSoftware];
    OthelloDefs.WriteString["="L];
    Format.NetworkNumber[Push, net, octal];
    END;
      
  WriteProductNetNumber: PROCEDURE [net: System.NetworkNumber] =
    BEGIN
    Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN
      THROUGH [s.length..6) DO OthelloDefs.WriteChar[' ]; ENDLOOP;
      OthelloDefs.WriteString[s];
      END;
    Format.NetworkNumber[Push, net, productSoftware];
    END;
      
  WriteNetNumber: PROCEDURE [net: System.NetworkNumber] =
    BEGIN
    Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN
      THROUGH [s.length..6) DO OthelloDefs.WriteChar[' ]; ENDLOOP;
      OthelloDefs.WriteString[s];
      END;
    Format.NetworkNumber[Push, net, octal];
    END;
  
  WriteNetAndHost: PROCEDURE [
    net: System.NetworkNumber, host: System.HostNumber] =
    BEGIN
    Append: Format.StringProc = BEGIN String.AppendString[temp, s]; END;
    temp: STRING = [50];
    Format.NetworkNumber[Append, net, productSoftware];
    String.AppendChar[temp, '.];
    Format.HostNumber[Append, host, productSoftware];
    String.AppendChar[temp, '.];
    THROUGH [temp.length..25) DO OthelloDefs.WriteChar[' ]; ENDLOOP;
    OthelloDefs.WriteString[temp];
    END;
  
  WriteDecimal: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 0]; END;

  D4: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 4]; END;
  
  WriteNumber: PROCEDURE [n, radix, width: CARDINAL] =
    BEGIN
    temp: STRING = [25];
    String.AppendNumber[temp, n, radix];
    THROUGH [temp.length..width) DO OthelloDefs.WriteChar[' ]; ENDLOOP;
    OthelloDefs.WriteString[temp];
    END;

  Trouble: ERROR [reason: LONG STRING] = CODE;
  GetAddress: PROCEDURE [host: LONG STRING, socket: System.SocketNumber]
    RETURNS [addr: System.NetworkAddress] =
    BEGIN
    localFailed: BOOLEAN ← FALSE;
    IF host = NIL THEN ERROR Trouble ["NIL => Address Fault"L];
    addr ← Unformat.NetworkAddress[host, octal !
      Unformat.Error => BEGIN localFailed ← TRUE; CONTINUE; END ];
    IF localFailed THEN
      BEGIN
      addr ← AddressTranslation.StringToNetworkAddress[host !
        AddressTranslation.Error =>
	  BEGIN
	  temp: STRING = [200];
	  proc: Format.StringProc = {String.AppendString[temp, s]};
          AddressTranslation.PrintError[errorRecord, proc];
	  ERROR Trouble[temp];
	  END].addr;
        addr.socket ← socket;  -- CH returns trash in socket
      END;
    IF addr.socket = System.nullSocketNumber THEN addr.socket ← socket;
    END;

  echoName: LONG STRING ← NIL;
  Commands: PROCEDURE [index: CARDINAL] =
    BEGIN
    SELECT index FROM
      0 =>
        BEGIN
        OthelloDefs.MyNameIs[myNameIs: "Echo User"L, myHelpIs: "NS echo user"L];
	EchoUser[];
	END;
      1 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Routing Tables"L,
	  myHelpIs: "Show this machine's NS routing table"L];
	PrintRoutingTable[];
	END;
      2 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Print Device Statistics"L,
	  myHelpIs: "Print Device dependent Statistics for all drivers"L];
	PrintDeviceStats[];
	END;
      ENDCASE => OthelloDefs.IndexTooLarge;
    END;
   
  commandProcessor: OthelloDefs.CommandProcessor ← [Commands];
  -- initialization
  OthelloDefs.RegisterCommandProc[@commandProcessor];

  END.....