PupTool.mesa;
By Steve Temple on August 27, 1982 9:39 am
Adapted from PupHacks by Hal Murray
Last edited by Steve Temple, November 11, 1982 2:41 pm
Revised for Cedar 5.2 by Neil Gunther, February 27, 1985 3:40:54 pm PST

DIRECTORY
BasicTime: TYPE USING [Now],
Buttons: TYPE USING [Button, ButtonProc, Create],
Commander USING [CommandProc, Register],
Containers: TYPE USING [ChildXBound, ChildYBound, Container, Create],
Convert USING [RopeFromTime],
ConvertUnsafe: TYPE USING [AppendRope],
DriverDefs: TYPE USING [Network],
IO,
Labels: TYPE USING [Create, Label, Set, SetDisplayStyle],
Menus: TYPE USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
Process: TYPE USING [Pause, SecondsToTicks],
PupDefs: TYPE USING [DataWordsPerPupBuffer,
GetFreePupBuffer, GetPupAddress, GetPupContentsBytes,
MoveRopeToPupBuffer, ReturnFreePupBuffer, PupAddressToRope,
PupBuffer, PupNameTrouble, PupPackageDestroy,
PupPackageMake, PupRouterBroadcastThis, PupSocket,
PupSocketDestroy, PupSocketMake, SecondsToTocks,
SetPupContentsBytes],
PupRouterDefs: TYPE USING [GetRoutingTable, GetRoutingTableEntry,
RoutingTableEntry, RoutingTable],
PupTypes: TYPE USING [echoSoc, fillInPupAddress, fillInSocketID,
maxDataWordsPerGatewayPup, miscSrvSoc, PupAddress, PupSocketID],
Rope: TYPE USING [Concat, FromChar, Length, ROPE, Substr],
TypeScript: TYPE USING [Create],
VFonts: TYPE USING [CharWidth],
ViewerClasses: TYPE USING [Viewer, ViewerClassRec],
ViewerIO: TYPE USING [CreateViewerStreams],
ViewerOps: TYPE USING [CreateViewer, DestroyViewer, PaintViewer],
ViewerTools: TYPE USING [GetContents, MakeNewTextViewer, SetSelection];


PupTool: MONITOR

IMPORTS
BasicTime, Buttons, Commander, Containers, Convert, ConvertUnsafe, IO, Labels, Menus,
Process, PupDefs,
PupRouterDefs, Rope, TypeScript, VFonts, ViewerIO, ViewerOps, ViewerTools = BEGIN

entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 8;
entryHSpace: CARDINAL = 10;

-- a ToolHandle is a REF to the data for a particular instance of the tool.

ToolHandle: TYPE = REF ToolHandleRec;

ToolHandleRec: TYPE = RECORD [
 pleaseStop, echoInUse: BOOLFALSE, -- useful flags
 viewer, root, msg: ViewerClasses.Viewer, -- viewer and parent
 out: IO.STREAM,       -- text stream
 echoThrough: CONDITION];    -- echo process is done

  
MakeTool: PROC = BEGIN

 handle: ToolHandle ← NEW[ToolHandleRec];
 promptButton, textBox: ViewerClasses.Viewer;
 menu: Menus.Menu ← Menus.CreateMenu[1];
 height: CARDINAL ← 0;
 charSize: INTEGER = VFonts.CharWidth['0];
 initData: Rope.ROPE = "ME";

 PupDefs.PupPackageMake[];
  
 handle.root ← Containers.Create[[
  name: "Pup Tool",
  iconic: FALSE,
  menu: menu,
  column: left,
  scrollable: FALSE
  ]];
  

 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "Destroy", proc: QuitProc, fork: FALSE]];
   
 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "EchoOn", proc: EchoProc, fork: TRUE]];
      
 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "EchoOff", proc: StopProc, fork: FALSE]];

 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "Route", proc: RouteProc, fork: FALSE]];
   
 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "ToName", proc: ToNameProc, fork: FALSE]];
   
 Menus.AppendMenuEntry[menu: menu, line: 0,
  entry: Menus.CreateEntry[clientData: handle,
  name: "ToAddress", proc: ToAddressProc, fork: FALSE]];  

 height ← height + entryVSpace;
   
 promptButton ← Buttons.Create[
  info: [name: "Name or Address", wy: height, wh: entryHeight,
    wx: 10*charSize, parent: handle.root],
  proc: Prompt,
  clientData: handle];
    
 handle.viewer ← ViewerTools.MakeNewTextViewer[[
  parent: handle.root,
  wx: promptButton.wx + promptButton.ww + entryHSpace,
  wy: height+2, -- (hack: add 2 to align text with button baseline)
  ww: 50*charSize, -- 50 digits worth of width
  wh: entryHeight,
  data: initData,
  scrollable: FALSE,
  border: FALSE
  ]];
  
 height ← height + entryHeight + entryVSpace;
   
 handle.msg ← Labels.Create[[
  parent: handle.root,
  wx: 10*charSize,
  wy: height,
  ww: 80*charSize, -- 80 digits worth of width
  wh: entryHeight,
  border: FALSE
  ]];
  
 height ← height + entryHeight + entryVSpace; -- interline spacing

 textBox ← TypeScript.Create [[
parent: handle.root,
border: FALSE,
wy: height
]];

Containers.ChildXBound[handle.root, textBox];
Containers.ChildYBound[handle.root, textBox];

ViewerOps.PaintViewer[handle.root, all];

 [, handle.out] ← ViewerIO.CreateViewerStreams[viewer: textBox, name: NIL];

PrintHeaderLine[handle.out];
END;


Prompt: Buttons.ButtonProc = TRUSTED BEGIN
 handle: ToolHandle ← NARROW[clientData]; -- get our data
 ViewerTools.SetSelection[handle.viewer];  -- force the selection
END;

PrintHeaderLine: PROC[out: IO.STREAM] = BEGIN
 meID: PupTypes.PupSocketID ← [0,0];
 me: PupTypes.PupAddress ← [,,[0,0]];
-- time: IO.GreenwichMeanTime ← Runtime.GetBcdTime[];

 me ← PupDefs.GetPupAddress[meID, "ME"];
IO.PutF[out, "PupTool of %s running on %s\n",
IO.rope[Convert.RopeFromTime[from: BasicTime.Now[]]],
IO.rope[PupDefs.PupAddressToRope[me]]];
END;

ErrorMsg: PROC[l: Labels.Label, r: Rope.ROPENIL] = BEGIN
IF Rope.Length[r] > 70 THEN r ← Rope.Substr[r, 0, 70];
IF Rope.Length[r] < 62 THEN r ← Rope.Concat["ERROR —> ", r];
 Labels.Set[l, r];
 Labels.SetDisplayStyle[l, $WhiteOnBlack];
 Process.Pause[Process.SecondsToTicks[1]];
 Labels.SetDisplayStyle[l, $BlackOnWhite];
 END;

ClearMsg: PROC[l: Labels.Label] = BEGIN
 Labels.Set[l, NIL];
 Labels.SetDisplayStyle[l, $BlackOnWhite];
 END;

InRope: PROC[v: ViewerClasses.Viewer] RETURNS [r: Rope.ROPE ← NIL] = BEGIN
 r ← ViewerTools.GetContents[v];
 END;

PutSep: PROC[out: IO.STREAM] = BEGIN
 IO.PutText[out, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"];
 END;

NewLine: PROC[out: IO.STREAM] = BEGIN
IO.PutChar[out, '\n];
END;

ErrorPupToRope: PROC[b: PupDefs.PupBuffer] RETURNS[s: Rope.ROPE]=
 BEGIN
 source: PupTypes.PupAddress ← b.source;
 IF b.pupType=error THEN
 BEGIN
 len: CARDINAL = PupDefs.GetPupContentsBytes[b];
 s ← IO.PutFR["[Error Pup, code=%b, from: ", IO.card[LOOPHOLE[b.errorCode, CARDINAL]]];
 s ← Rope.Concat[s, PupDefs.PupAddressToRope[source]];
 s ← Rope.Concat[s, "] "];
 FOR i: CARDINAL IN [0..len-2*(10+1+1)) DO
  s ← Rope.Concat[s, Rope.FromChar[b.errorText[i]]]; ENDLOOP;
 END
 ELSE
 s ← IO.PutFR["## Funny PupType = %b ##\n ", IO.card[LOOPHOLE[b.pupType, CARDINAL]]];
END;


PupBodyToRope: PROC[b: PupDefs.PupBuffer] RETURNS[s: Rope.ROPE]=
BEGIN
s ← NIL;
FOR i: CARDINAL IN [0..PupDefs.GetPupContentsBytes[b]) DO
s ← Rope.Concat[s, Rope.FromChar[b.pupChars[i]]]; ENDLOOP;
END;


EchoProc: Menus.MenuProc = TRUSTED
BEGIN OPEN PupDefs, PupTypes;

h: ToolHandle = NARROW[clientData];
bytesPerBuffer: CARDINAL;
funny, late, recv, sent, wrong: LONG CARDINAL ← 0;
me, where: PupAddress ← [,,echoSoc];
mySoc: PupSocket;
mySocID: PupSocketID ← [0,0];
packetNumber: CARDINAL ← 0;
routing: PupRouterDefs.RoutingTableEntry;
r: Rope.ROPE;
fudge: STRING ← [40];

IF EchoIsInUse[h] THEN RETURN;
ClearMsg[h.msg];
r ← InRope[h.viewer];
IF Rope.Length[r] = 0 THEN { ErrorMsg[h.msg, "Name needed"]; GOTO GetOut};
ConvertUnsafe.AppendRope[fudge, r];
where ← GetPupAddress[mySocID, r ! PupNameTrouble =>
{ErrorMsg[h.msg, e]; GOTO GetOut }];
mySoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[2]];
me ← mySoc.getLocalAddress[];

PutSep[h.out];
IO.PutF[h.out, "Echo to <%g> via [%g] => [", IO.rope[r], IO.rope[PupAddressToRope[me]]];
routing ← PupRouterDefs.GetRoutingTableEntry[where.net];
IF (routing.hop#0) AND (routing.network#NIL) THEN
IO.PutF[h.out, "%b#%b#] => [",
IO.card[LOOPHOLE[routing.network.netNumber.b, CARDINAL]],
IO.card[LOOPHOLE[routing.route, CARDINAL]]];
IO.PutF[h.out, "%g]\n", IO.rope[PupAddressToRope[where]]];

bytesPerBuffer ←
2*MIN[DataWordsPerPupBuffer[], maxDataWordsPerGatewayPup];
UNTIL EchoShouldStop[h] DO
FOR len: CARDINAL IN [0..bytesPerBuffer] UNTIL EchoShouldStop[h] 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 MOD 400B; ENDLOOP;
mySoc.put[b];
sent ← sent+1;
UNTIL (b←mySoc.get[])=NIL DO
SELECT TRUE FROM
(b.pupType#iAmEcho) =>
{ funny ← funny+1; ErrorMsg[h.msg, ErrorPupToRope[b]];
IO.PutChar[h.out, 'x]; GOTO Wrong };
((b.pupID.a#packetNumber)
OR (b.pupID.b#packetNumber)
OR (len#GetPupContentsBytes[b])) =>
{ IO.PutChar[h.out, '#]; late ← late+1 };
ENDCASE =>
BEGIN
FOR i: CARDINAL IN [0..len) DO
IF b.pupBytes[i]#(i MOD 400B) THEN
{ wrong ← wrong+1; IO.PutChar[h.out, '~]; GOTO Wrong };
ENDLOOP;
IO.PutChar[h.out, '!];
recv ← recv+1;
EXIT;
END;
ReturnFreePupBuffer[b];
REPEAT Wrong => NULL;
ENDLOOP;
IF b#NIL THEN ReturnFreePupBuffer[b] ELSE IO.PutChar[h.out, '?];
ENDLOOP;
NewLine[h.out];
ENDLOOP;
PupSocketDestroy[mySoc];
IO.PutF[h.out, "Out: %d, In: %d (%d%%)\n", IO.card[sent], IO.card[recv], IO.card[(recv*100)/sent]];
IF late#0 THEN IO.PutF[h.out, "Late: %d (%d%%)\n", IO.card[late], IO.card[(late*100)/sent]];
IF funny#0 THEN IO.PutF[h.out, "%d funny\n", IO.card[funny]];
IF wrong#0 THEN IO.PutF[h.out, "%d wrong data\n", IO.card[wrong]];
EchoUserStopped[h];
EXITS GetOut => {h: ToolHandle = NARROW[clientData]; EchoUserStopped[h]};
END;

RouteProc: Menus.MenuProc = TRUSTED BEGIN
pupRt: PupRouterDefs.RoutingTable;
h: ToolHandle ← NARROW[clientData];
k: CARDINAL ← 0;

PutSep[h.out];
ClearMsg[h.msg];
IO.PutText[h.out, "Local PupRouting Table\n"];
IO.PutText[h.out, "| Net Via Hops | Net Via Hops | Net Via Hops |\n"];
IO.PutText[h.out, "|-------------------|-------------------|-------------------|\n"];
pupRt ← PupRouterDefs.GetRoutingTable[];

FOR i: CARDINAL IN [0..pupRt.length] DO
r: PupRouterDefs.RoutingTableEntry = @pupRt[i];
network: DriverDefs.Network = r.network;
IF network=NIL THEN LOOP;
IF k=0 THEN IO.PutChar[h.out, '|];
IO.PutF[h.out, "%4b%4b#", IO.card[i],
IO.card[LOOPHOLE[network.netNumber.b, CARDINAL]]];

IO.PutF[h.out, "%03b#%4b |",
IO.card[IF r.hop#0 THEN r.route ELSE network.hostNumber], IO.card[r.hop]];
IF (k←k+1)=3 THEN {NewLine[h.out]; k𡤀};
ENDLOOP;
IF k#0 THEN NewLine[h.out];
END;


ToNameProc: Menus.MenuProc = TRUSTED
BEGIN OPEN PupTypes, PupDefs;
h: ToolHandle = NARROW[clientData];
soc: PupSocket;
b: PupBuffer;
addrID: PupSocketID ← [0, 0];
addr: PupAddress ← [, , [0, 0]];
hit: BOOLEANFALSE;
r: Rope.ROPE;

r ← InRope[h.viewer];
ClearMsg[h.msg];
IF Rope.Length[r] = 0 THEN BEGIN ErrorMsg[h.msg, "Address needed"]; RETURN; END;
addr ← GetPupAddress[addrID, r ! PupNameTrouble =>
      BEGIN
      ErrorMsg[h.msg, 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 ← addr;
SetPupContentsBytes[b, 2*SIZE[PupAddress]];
PupRouterBroadcastThis[b];
UNTIL hit OR (b ← soc.get[]) = NIL DO
SELECT b.pupType FROM
  addressIs =>
BEGIN
  hit ← TRUE;
  PutSep[h.out];
IO.PutF[h.out, "Name of <%g> is: %g\n", IO.rope[r], IO.rope[PupBodyToRope[b]]];
END;
  nameError =>
BEGIN
  hit ← TRUE;
  ErrorMsg[h.msg, PupBodyToRope[b]];
END;
ENDCASE => ErrorMsg[h.msg, ErrorPupToRope[b]];
 ReturnFreePupBuffer[b];
ENDLOOP;
IF ~hit THEN ErrorMsg[h.msg, "No Response that try"];
ENDLOOP;
PupSocketDestroy[soc];
EXITS NoName => NULL;
END;

  
ToAddressProc: Menus.MenuProc = TRUSTED
BEGIN OPEN PupTypes, PupDefs;
h: ToolHandle = NARROW[clientData];
soc: PupSocket;
b: PupBuffer;
hit: BOOLEANFALSE;
r: Rope.ROPE;

r ← InRope[h.viewer];
ClearMsg[h.msg];
IF Rope.Length[r] = 0 THEN BEGIN ErrorMsg[h.msg, "Name needed"]; 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[];
PupDefs.MoveRopeToPupBuffer[b, r];
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;
  PutSep[h.out];
IO.PutF[h.out, "Address of <%g> is: ", IO.rope[r]];
FOR i: CARDINAL IN [0..n) DO
IF i # 0 THEN IO.PutText[h.out, ", "];
IO.PutRope[h.out, PupAddressToRope[addresses[i]]];
ENDLOOP;
  NewLine[h.out];
END;
  nameError =>
BEGIN
  hit ← TRUE;
  ErrorMsg[h.msg, PupBodyToRope[b]];
END;
ENDCASE => ErrorMsg[h.msg, ErrorPupToRope[b]];
 ReturnFreePupBuffer[b];
ENDLOOP;
IF ~hit THEN ErrorMsg[h.msg, "No Response that try"];
ENDLOOP;
PupSocketDestroy[soc];
END;

QuitProc: Menus.MenuProc = TRUSTED
BEGIN
 h: ToolHandle = NARROW[clientData];
 StopEchoUser[h];
 ViewerOps.DestroyViewer[h.root];
 PupDefs.PupPackageDestroy[];
END;

StopProc: Menus.MenuProc = TRUSTED
BEGIN
 h: ToolHandle = NARROW[clientData];
 StopEchoUser[h];
END;

StopEchoUser: ENTRY PROC [h: ToolHandle] =
BEGIN
 h.pleaseStop ← TRUE;
UNTIL NOT h.echoInUse DO WAIT h.echoThrough ENDLOOP;
 h.pleaseStop ← FALSE;
END;

EchoShouldStop: ENTRY PROC [h: ToolHandle] RETURNS[BOOLEAN] = INLINE
{RETURN[h.pleaseStop]};

EchoUserStopped: ENTRY PROC [h: ToolHandle] =
 {h.echoInUse ← FALSE; NOTIFY h.echoThrough};

EchoIsInUse: ENTRY PROC [h: ToolHandle] RETURNS [BOOLEAN] =
 {IF h.echoInUse THEN RETURN [TRUE]; h.echoInUse ← TRUE; RETURN[FALSE]};


DoIt: Commander.CommandProc = TRUSTED BEGIN
 MakeTool[] 
END;

Commander.Register[key: "PupTool", proc: DoIt, doc: "Simple network interogation program"];

MakeTool[]

END.