-- File: PupHacks.mesa,  Last Edit: HGM  January 24, 1981  2:16 PM
-- From OthelloPup.mesa of  August 29, 1980  9:21 AM

DIRECTORY
  -- Things from the system
  IODefs,
  Process USING [Detach, Yield],
  Runtime USING [GetBcdTime],
  String USING [AppendLongDecimal],
  Time USING [Append, Unpack],
  -- Pup things...
  DriverDefs USING [Network],
  PupDefs USING [
    AppendPupAddress, DataWordsPerPupBuffer, GetFreePupBuffer, GetPupAddress,
    GetPupContentsBytes, MoveStringBodyToPupBuffer, ReturnFreePupBuffer,
    PupBuffer, PupNameTrouble, PupPackageDestroy, PupPackageMake,
    PupRouterBroadcastThis, PupSocket, PupSocketDestroy, PupSocketMake,
    SecondsToTocks, SetPupContentsBytes],
  PupRouterDefs USING [
    EnumerateRoutingTable, GetRoutingTableEntry, RoutingTableEntry],
  PupTypes USING [
    echoSoc, fillInPupAddress, fillInSocketID, maxDataWordsPerGatewayPup,
    miscSrvSoc, PupAddress];

PupHacks: PROGRAM
  IMPORTS IODefs, Process, Runtime, String, Time, PupDefs, PupRouterDefs =
  BEGIN OPEN IODefs;

  name: STRING ← [40]; -- Global so ESC will work

  AddressToName: PROCEDURE =
    BEGIN OPEN PupTypes, PupDefs;
    soc: PupSocket;
    b: PupBuffer;
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    WriteString["Address to Name for: "L];
    ReadID[name ! Rubout => {WriteLine["  XXX"L]; GOTO NoName; }];
    IF name.length = 0 THEN BEGIN WriteLine["Address needed."L]; RETURN; END;
    GetPupAddress[
      @a, name ! PupNameTrouble => BEGIN WriteLine[e]; GOTO NoName; END];
    soc ← PupSocketMake[fillInSocketID, fillInPupAddress, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← addressLookup;
      b.pupID ← [0, 0];
      b.dest.socket ← PupTypes.miscSrvSoc;
      b.source ← soc.getLocalAddress[];
      b.address ← a;
      SetPupContentsBytes[b, 2*SIZE[PupAddress]];
      PupRouterBroadcastThis[b];
      UNTIL hit OR (b ← soc.get[]) = NIL DO
	SELECT b.pupType FROM
	  addressIs =>
	    BEGIN hit ← TRUE; WriteString[" => "L]; PrintBodyAsText[b]; END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    WriteString[" => ERROR: "L];
	    PrintBodyAsText[b];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteLine["."L];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN WriteLine["No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    EXITS NoName => NULL;
    END;

  EchoUser: PROCEDURE =
    BEGIN OPEN PupDefs, PupTypes;
    bytesPerBuffer: CARDINAL;
    funny, late, recv, sent, wrong: LONG CARDINAL ← 0;
    me, where: PupAddress ← [, , echoSoc];
    mySoc: PupSocket;
    packetNumber: CARDINAL ← 0;
    pleaseStop: BOOLEAN ← FALSE;
    routing: PupRouterDefs.RoutingTableEntry;
    Watch: PROCEDURE = {[] ← ReadChar[]; pleaseStop ← TRUE};

    WriteString["Echo to: "L];
    ReadID[name ! Rubout => {WriteLine["  XXX"L]; GOTO NoName; }];
    IF name.length = 0 THEN BEGIN WriteLine["Name needed"L]; RETURN; END;
    GetPupAddress[
      @where, name !
      PupNameTrouble => {WriteString[" ***** "L]; WriteLine[e]; GOTO NoName}];
    mySoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[2]];
    me ← mySoc.getLocalAddress[];
    WriteString[".   ["L];
    PrintPupAddress[me];
    WriteString["] => ["L];
    routing ← PupRouterDefs.GetRoutingTableEntry[where.net];
    IF routing.hop # 0 THEN
      BEGIN -- UGH
      WriteNumber[routing.network.netNumber.b, [8, FALSE, TRUE, 0]];
      WriteChar['#];
      WriteNumber[routing.route, [8, FALSE, TRUE, 0]];
      WriteChar['#];
      WriteString["] => ["L];
      END;
    PrintPupAddress[where];
    WriteLine["]..."L];
    Process.Detach[FORK Watch[]];
    bytesPerBuffer ← 2*MIN[DataWordsPerPupBuffer[], maxDataWordsPerGatewayPup];
    UNTIL pleaseStop DO
      FOR len: CARDINAL IN [0..bytesPerBuffer] UNTIL pleaseStop DO
	b: PupBuffer ← GetFreePupBuffer[];
	b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
	b.pupType ← echoMe;
	SetPupContentsBytes[b, len];
	FOR i: CARDINAL IN [0..len) DO b.pupBytes[i] ← i; ENDLOOP;
	mySoc.put[b];
	sent ← sent + 1;
	Process.Yield[]; -- be sure we don't hog machine
	UNTIL (b ← mySoc.get[]) = NIL DO
	  SELECT TRUE FROM
	    (b.pupType # iAmEcho) => {funny ← funny + 1; PrintErrorPup[b]};
	    ((b.pupID.a # packetNumber) OR (b.pupID.b # packetNumber) OR
	      (len # GetPupContentsBytes[b])) => {WriteChar['#]; late ← late + 1};
	    ENDCASE =>
	      BEGIN
	      FOR i: CARDINAL IN [0..len) DO
		IF b.pupBytes[i] # (i MOD 400B) THEN {
		  wrong ← wrong + 1; WriteChar['~]; GOTO Wrong};
		ENDLOOP;
	      WriteChar['!];
	      recv ← recv + 1;
	      EXIT;
	      END;
	  ReturnFreePupBuffer[b];
	  REPEAT Wrong => NULL;
	  ENDLOOP;
	IF b # NIL THEN ReturnFreePupBuffer[b] ELSE WriteChar['?];
	ENDLOOP;
      WriteCR[];
      ENDLOOP;
    PupSocketDestroy[mySoc];
    WriteString["Out: "L];
    WriteLongNumber[sent];
    WriteString[", In: "L];
    WriteLongNumber[recv];
    WriteString[" ("L];
    WriteLongNumber[(recv*100)/sent];
    WriteLine["%)"L];
    IF late # 0 THEN
      BEGIN
      WriteString["Late: "L];
      WriteLongNumber[late];
      WriteString[" ("L];
      WriteLongNumber[(late*100)/sent];
      WriteLine["%)"L];
      END;
    IF funny # 0 THEN {WriteLongNumber[funny]; WriteLine[" funny"L]};
    IF wrong # 0 THEN {WriteLongNumber[wrong]; WriteLine[" wrong data"L]};
    EXITS NoName => NULL;
    END;

  NameToAddress: PROCEDURE =
    BEGIN OPEN PupTypes, PupDefs;
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    WriteString["Name to Address for: "L];
    ReadID[name ! Rubout => {WriteLine["  XXX"L]; GOTO NoName; }];
    IF name.length = 0 THEN BEGIN WriteLine["Name needed"L]; RETURN; END;
    soc ← PupSocketMake[fillInSocketID, fillInPupAddress, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← nameLookup;
      b.pupID ← [0, 0];
      b.dest.socket ← PupTypes.miscSrvSoc;
      b.source ← soc.getLocalAddress[];
      MoveStringBodyToPupBuffer[b, name];
      PupRouterBroadcastThis[b];
      UNTIL hit OR (b ← soc.get[]) = NIL DO
	SELECT b.pupType FROM
	  nameIs =>
	    BEGIN
	    n: CARDINAL ← GetPupContentsBytes[b]/(2*SIZE[PupAddress]);
	    addresses: LONG POINTER TO ARRAY [0..0) OF PupAddress ←
	      LOOPHOLE[@b.pupBody];
	    hit ← TRUE;
	    WriteString[" => "L];
	    FOR i: CARDINAL IN [0..n) DO
	      IF i # 0 THEN WriteString[", "L];
	      PrintPupAddress[addresses[i]];
	      ENDLOOP;
	    END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    WriteString[" => ERROR: "L];
	    PrintBodyAsText[b];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteCR[];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN WriteLine["No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    EXITS NoName => NULL;
    END;

  RoutingTable: PROCEDURE =
    BEGIN
    PrintOne: PROCEDURE [rte: PupRouterDefs.RoutingTableEntry] =
      BEGIN
      network: DriverDefs.Network ← rte.network;
      IF network = NIL THEN RETURN;
      nets ← nets + 1;
      IF k = 0 THEN WriteChar['|];
      O4[rte.net];
      O4[network.netNumber.b];
      WriteChar['#];
      IF rte.hop # 0 THEN O3Z[rte.route] ELSE O3Z[network.hostNumber];
      WriteChar['#];
      O4[rte.hop];
      WriteString["  |"L];
      IF (k ← k + 1) = 3 THEN BEGIN WriteCR[]; k ← 0; END;
      END;
    k, nets: CARDINAL ← 0;
    WriteCR[];
    WriteLine["  Local Routing Table."L];
    WriteLine["|  Net   Via   Hops |  Net   Via   Hops |  Net   Via   Hops |"L];
    WriteLine["|-------------------|-------------------|-------------------|"L];
    PupRouterDefs.EnumerateRoutingTable[PrintOne];
    IF k # 0 THEN WriteCR[];
    IF nets > 1 THEN
      BEGIN
      WriteString["There are "L];
      WriteDecimal[nets - 1];
      WriteLine[" active networks."L];
      END;
    END;

  PrintErrorPup: PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    source: PupTypes.PupAddress ← b.source;
    WriteCR[];
    IF b.pupType = error THEN
      BEGIN
      len: CARDINAL = PupDefs.GetPupContentsBytes[b];
      WriteString["[Error Pup, code="L];
      WriteOctal[b.errorCode];
      WriteString[", from: "L];
      PrintPupAddress[source];
      WriteString["] "L];
      FOR i: CARDINAL IN [0..len - 2*(10 + 1 + 1)) DO
	WriteChar[b.errorText[i]]; ENDLOOP;
      END
    ELSE
      BEGIN
      WriteString[" ***** "L];
      WriteString["Funny PupType = "L];
      WriteOctal[b.pupType];
      WriteString[" ***** "L];
      END;
    WriteCR[];
    END;

  PrintPupAddress: PROCEDURE [a: PupTypes.PupAddress] =
    BEGIN
    buffer: STRING ← [50];
    PupDefs.AppendPupAddress[buffer, a];
    WriteString[buffer];
    END;

  PrintBodyAsText: PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    FOR i: CARDINAL IN [0..PupDefs.GetPupContentsBytes[b]) DO
      WriteChar[b.pupChars[i]]; ENDLOOP;
    END;

  CommandLoop: PROCEDURE =
    BEGIN
    PupDefs.PupPackageMake[];
    PrintHeaderLine[];
    DO
      c: CHARACTER;
      WriteChar['>];
      c ← ReadChar[];
      SELECT c FROM
	'A, 'a => AddressToName[];
	'E, 'e => EchoUser[];
	'N, 'n => NameToAddress[];
	'R, 'r => RoutingTable[];
	'Q, 'q => BEGIN WriteLine["Quit."L]; EXIT; END;
	'H, 'h, '? =>
	  WriteLine[
	    "
Commands are: A(ddress to Name), E(cho), N(ame to Address), or R(outing).
H(elp) or ? prints this message.  Q(uit) gets you out of here."L];
	ENDCASE => WriteLine["? XXX"L];
      ENDLOOP;
    PupDefs.PupPackageDestroy[];
    END;

  PrintHeaderLine: PROCEDURE =
    BEGIN
    version: STRING = [20];
    me: PupTypes.PupAddress ← [, , [0, 0]];
    WriteString[" PupHacks of "L];
    Time.Append[version, Time.Unpack[Runtime.GetBcdTime[]]];
    WriteString[version];
    WriteString[" running on "L];
    PupDefs.GetPupAddress[@me, "ME"L];
    PrintPupAddress[me];
    WriteLine["."];
    END;

  WriteLongNumber: PROCEDURE [n: LONG CARDINAL] =
    BEGIN
    temp: STRING = [20];
    String.AppendLongDecimal[temp, n];
    WriteString[temp];
    END;

  -- prints 4 chars, octal, no trailing B

  O4: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, [8, FALSE, TRUE, 4]]; END;

  -- prints 3 chars, octal, leading zeros

  O3Z: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, [8, TRUE, TRUE, 3]]; END;

  WriteCR: PROCEDURE = BEGIN WriteLine[""L]; END;

  -- initialization

  CommandLoop[];
  END.