-- Copyright (C) 1984, 1985, 1986  by Xerox Corporation. All rights reserved. 
-- PupToTcpTool.mesa, WIrish, 30-May-86 21:51:36
-- From: NameLookupTool.mesa
-- Please don't forget to update the herald....

DIRECTORY
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem,
    StringItem],
  Heap USING [systemZone],
  MsgSW USING [Post],
  Put USING [Char, CR, Text, Line, LongDecimal],
  String USING [AppendChar, AppendNumber, AppendString, Copy, InvalidNumber, Length, StringToDecimal, WordsForString],
  Time USING [AppendCurrent],
  Tool USING [
    Create, MakeSWsProc, MakeMsgSW, MakeFormSW, MakeFileSW, UnusedLogName],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  NameServerDefs USING [
    nameStatsRequest, nameStatsReply, NameStatsEntry,
    nameToCacheRequest, addressToCacheRequest, hereIsCacheEntry],
  PupWireFormat USING [BcplToMesaLongNumber],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, SecondsToTocks,
    GetPupContentsBytes, SetPupContentsWords, MoveStringBodyToPupBuffer,
    AppendPupAddress, AppendErrorPup, GetPupAddress, PupNameTrouble],
  PupTypes USING [PupAddress, fillInNetID, fillInSocketID, allHosts, miscSrvSoc];

PupToTcpTool: PROGRAM
  IMPORTS FormSW, Heap, MsgSW, Put, String, Time, Tool, Buffer, PupWireFormat, PupDefs
  =
  BEGIN OPEN PupDefs, PupTypes;

  z: UNCOUNTED ZONE = Heap.systemZone;

  msg, form, log: Window.Handle;

  data: LONG POINTER TO Data ← NIL;

  Data: TYPE = RECORD [
    where: PupAddress,
    target: LONG STRING,
    name: LONG STRING,
    address: LONG STRING,
    pupOrTcp: LONG STRING];

  Stats: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Pup Name Server Statistics "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 2];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← NameServerDefs.nameStatsRequest;
      b.pup.pupID ← [0, 0];
      b.pup.pupWords[0] ← 0;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          NameServerDefs.nameStatsReply =>
            BEGIN
            nse: LONG POINTER TO NameServerDefs.NameStatsEntry;
            hit ← TRUE;
            nse ← LOOPHOLE[@b.pup.pupWords];
            PrintInfo[
              "Requests"L, PupWireFormat.BcplToMesaLongNumber[nse.nameRequests]];
            PrintInfo[
              "Directories sent"L, PupWireFormat.BcplToMesaLongNumber[
              nse.directoriesSend]];
            PrintInfo[
              "Cache hits"L, PupWireFormat.BcplToMesaLongNumber[nse.cacheHits]];
            PrintInfo[
              "Cache misses"L, PupWireFormat.BcplToMesaLongNumber[
              nse.cacheMisses]];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteCR[];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  Version: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Pup Network Directory Version "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 2];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← netDirVersion;
      b.pup.pupID ← [0, 0];
      b.pup.pupWords[0] ← 0;
      SetPupContentsWords[b, 1];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          netDirVersion =>
            BEGIN
            hit ← TRUE;
            WriteString["Pup-network.directory version is "L];
            WriteDecimal[b.pup.pupWords[0]];
	    IF GetPupContentsBytes[b] > 2 THEN
	      BEGIN
	      WriteLine["."L];
              WriteString["Pup-network.big version is "L];
              WriteDecimal[b.pup.pupWords[1]];
	      END;
            END;
          ENDCASE => PrintErrorPup[b];
        WriteLine["."L];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  NameToAddress: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    IF data.name = NIL OR data.name.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Name needed"L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Name=>Address "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 2];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← nameLookup;
      MoveStringBodyToPupBuffer[b, data.name];
      b.pup.pupID ← [0, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          nameIs =>
            BEGIN
            i, n: CARDINAL;
            addresses: LONG POINTER TO ARRAY [0..0) OF PupAddress ←
              LOOPHOLE[@b.pup.pupBody];
            hit ← TRUE;
            WriteString[data.name];
            WriteString[" => "L];
            n ← GetPupContentsBytes[b]/(2*SIZE[PupAddress]);
            FOR i IN [0..n) DO
              IF i # 0 THEN WriteString[", "L];
              PrintPupAddress[@addresses[i]];
              ENDLOOP;
            END;
          nameError =>
            BEGIN
            hit ← TRUE;
            WriteString[data.name];
            WriteString[" => ERROR: "L];
            PrintBodyAsText[b];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteCR[];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  NameToCache: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    IF data.name = NIL OR data.name.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Name needed"L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Name=>CacheEntry "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 2];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← NameServerDefs.nameToCacheRequest;
      MoveStringBodyToPupBuffer[b, data.name];
      b.pup.pupID ← [0, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          NameServerDefs.hereIsCacheEntry =>
            BEGIN
            hit ← TRUE;
	    PrintCacheEntry[b];
            END;
          nameError =>
            BEGIN
            hit ← TRUE;
            WriteString[data.name];
            WriteString[" => ERROR: "L];
            PrintBodyAsText[b];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteCR[];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  AddressToName: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    IF data.address = NIL OR data.address.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Address needed"L]; RETURN; END;
    GetPupAddress[
      @a, data.address !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Address=>Name "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 10];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← addressLookup;
      b.pup.pupID ← [0, 0];
      b.pup.address ← a;
      SetPupContentsWords[b, SIZE[PupAddress]];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          addressIs =>
            BEGIN
            hit ← TRUE;
            WriteString[data.address];
            WriteString[" => "L];
            PrintBodyAsText[b];
            END;
          nameError =>
            BEGIN
            hit ← TRUE;
            WriteString[data.address];
            WriteString[" => ERROR: "L];
            PrintBodyAsText[b];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteLine["."L];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    EXITS Trouble => NULL;
    END;

  AddressToCache: FormSW.ProcType =
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    IF data.address = NIL OR data.address.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Address needed"L]; RETURN; END;
    GetPupAddress[
      @a, data.address !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Address=>CacheEntry "L];
    IF ~FindPath[] THEN RETURN;
    pool ← Buffer.MakePool[send: 1, receive: 2];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← NameServerDefs.addressToCacheRequest;
      b.pup.pupID ← [0, 0];
      b.pup.address ← a;
      SetPupContentsWords[b, SIZE[PupAddress]];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          NameServerDefs.hereIsCacheEntry =>
            BEGIN
            hit ← TRUE;
            PrintCacheEntry[b];
            END;
          nameError =>
            BEGIN
            hit ← TRUE;
            WriteString[data.address];
            WriteString[" => ERROR: "L];
            PrintBodyAsText[b];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteLine["."L];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    EXITS Trouble => NULL;
    END;

  XeroxClassA: CARDINAL = 13;
  
  PupToTcp: FormSW.ProcType =
    BEGIN
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    IF data.pupOrTcp = NIL OR data.pupOrTcp.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Conversion value needed"L]; RETURN; END;
    GetPupAddress[
      @a, data.pupOrTcp !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteLine[" PUP=>TCP"L];
    WriteString[data.pupOrTcp];
    WriteString[" => "L];
    PrintPupAddress[@a];
    WriteString[" => "L];
    WriteDecimal[XeroxClassA];
    WriteChar['.];
    WriteDecimal[a.net/100B];
    WriteChar['.];
    WriteDecimal[(a.net MOD 100B)*4];
    WriteChar['.];
    WriteDecimal[a.host];
    WriteCR[];
    EXITS Trouble => NULL;
    END;
    
  TcpToPup: FormSW.ProcType =
    BEGIN
    tempString: STRING = [50];
    numberString: STRING = [20];
    tcp: ARRAY [0..4) OF CARDINAL ← ALL[0];
    i: CARDINAL ← 0;
    copyNextNumber: PROCEDURE =
      BEGIN
      j: CARDINAL ← 1;
      String.Copy[numberString, "0"L];
      -- skip over junk
      WHILE (i < String.Length[tempString])
            AND ((tempString.text[i] < '0)
	    OR (tempString.text[i] > '9)) DO
	    i ← i + 1;
	    ENDLOOP;
      -- copy over number
      WHILE (i < String.Length[tempString])
            AND (tempString.text[i] >= '0)
	    AND (tempString.text[i] <= '9) DO
	    String.AppendChar[numberString, tempString.text[i]];
	    i ← i + 1;
	    j ← j + 1;
	    ENDLOOP;
      END;
    String.Copy[tempString, data.pupOrTcp];
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteLine[" TCP=>PUP"L];
    WriteString[tempString];
    WriteString[" => "L];
    FOR j: CARDINAL IN [0..4) DO
      copyNextNumber[];
      tcp[j] ← String.StringToDecimal[numberString !
                String.InvalidNumber => {tcp[j] ← 0; CONTINUE;}];
      WriteDecimal[tcp[j]];
      IF j < 3 THEN WriteChar['.];
      ENDLOOP;
    WriteString[" => "L];
    IF tcp[0] # XeroxClassA THEN
       BEGIN
       WriteLine["Error: XEROX-NET is 013.rrr.rrr.rrr!"L];
       RETURN;
       END;
    WriteOctal[tcp[1]*100B + tcp[2]/4];
    WriteChar['#];
    WriteOctal[tcp[3] + (tcp[2] MOD 4)*400B];
    WriteChar['#];
    BEGIN
    pool: Buffer.AccessHandle;
    soc: PupSocket;
    b: PupBuffer;
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    a.net ← LOOPHOLE[tcp[1]*100B + tcp[2]/4];
    a.host ← LOOPHOLE[tcp[3] + (tcp[2] MOD 4)*400B];
    WriteString[" => "L];
    IF ~SilentFindPath[] THEN 
       BEGIN
       WriteString["Not PUP Registered"L];
       RETURN;
       END;
    pool ← Buffer.MakePool[send: 1, receive: 10];
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← addressLookup;
      b.pup.pupID ← [0, 0];
      b.pup.address ← a;
      SetPupContentsWords[b, SIZE[PupAddress]];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        IF data.where # b.pup.source THEN
          BEGIN
          WriteString["Reply from: "L];
          PrintPupAddress[@b.pup.source];
          WriteCR[];
          END;
        SELECT b.pup.pupType FROM
          addressIs =>
            BEGIN
            hit ← TRUE;
            PrintBodyAsText[b];
            END;
          nameError =>
            BEGIN
            hit ← TRUE;
            WriteString[data.address];
            WriteString[" Not found."L];
            END;
          ENDCASE => PrintErrorPup[b];
        WriteCR[];
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;
    END;
    
  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN data;
    data.where ← [fillInNetID, allHosts, miscSrvSoc];
    IF data.target = NIL OR data.target.length = 0 THEN
      BEGIN WriteLine["via broadcasting on local net(s)."L]; RETURN[TRUE]; END
    ELSE BEGIN WriteString["from "L]; END;
    WriteString[target];
    WriteChar['=];
    GetPupAddress[
      @where, target !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    PrintPupAddress[@where];
    WriteLine["."L];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  SilentFindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN data;
    data.where ← [fillInNetID, allHosts, miscSrvSoc];
    GetPupAddress[
      @where, target !
      PupNameTrouble =>
        BEGIN GOTO Trouble; END];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  -- IO things

  WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END;

  WriteCR: PROCEDURE = BEGIN Put.CR[log]; END;

  WriteString: PROCEDURE [s: LONG STRING] = BEGIN Put.Text[log, s]; END;

  WriteLine: PROCEDURE [s: LONG STRING] = BEGIN Put.Line[log, s]; END;

  WriteDecimal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END;

  WriteOctal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END;

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

  WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] = INLINE
    BEGIN Put.LongDecimal[log, n]; END;

  D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;

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

  O3Z: PROCEDURE [n: CARDINAL] =
    BEGIN
    temp: STRING = [25];
    String.AppendNumber[temp, n, 8];
    THROUGH [temp.length..3) DO WriteChar['0]; ENDLOOP;
    WriteString[temp];
    END;

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

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

  O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END;

  WriteCurrentDateAndTime: PROCEDURE =
    BEGIN temp: STRING = [20]; Time.AppendCurrent[temp]; WriteString[temp]; END;

  PrintInfo: PROCEDURE [s: LONG STRING, n: LONG CARDINAL] =
    BEGIN
    IF n = 0 THEN RETURN;
    WriteString[s];
    WriteString[" = "L];
    WriteLongDecimal[n];
    WriteLine["."L];
    END;

  PrintPupAddress: PROCEDURE [a: LONG POINTER TO PupAddress] =
    BEGIN temp: STRING = [40]; AppendPupAddress[temp, a↑]; WriteString[temp]; END;

  PrintErrorPup: PROCEDURE [b: PupBuffer] =
    BEGIN temp: STRING = [200]; AppendErrorPup[temp, b]; WriteString[temp]; END;

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

  PrintCacheEntry: PROCEDURE [b: PupBuffer] =
    BEGIN
    p: LONG POINTER ← @b.pup.pupWords;
    n: CARDINAL ← 0;
    version: CARDINAL;
    names: CARDINAL;
    addrs: CARDINAL;
    version ← (p+n)↑;  -- File version number
    n ← n + SIZE[CARDINAL];
    names ← (p+n)↑;
    n ← n + SIZE[CARDINAL];
    IF names = 0 THEN WriteString["??"L];
    FOR i: CARDINAL IN [0..names) DO
      s: LONG STRING = LOOPHOLE[p+n];
      words: CARDINAL ← String.WordsForString[s.length];
      IF i # 0 THEN WriteString[", "L];
      WriteString[s];
      n ← n + words;
      ENDLOOP;
    WriteString[" <=> "L];
    addrs ← (p+n)↑;
    n ← n + SIZE[CARDINAL];
    IF addrs = 0 THEN WriteString["??"L];
    FOR i: CARDINAL IN [0..addrs) DO
      a: LONG POINTER TO PupAddress ← LOOPHOLE[(p+n)];
      IF i # 0 THEN WriteString[", "L];
      PrintPupAddress[a];
      n ← n + SIZE[PupAddress];
      ENDLOOP;
    END;

  Init: PROCEDURE =
    BEGIN
    herald: STRING = "PUPtoTCP of 30-May-86 21:51:31"L;
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logFileName: STRING = [40];
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    Tool.UnusedLogName[logFileName, "PupToTcp.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName, allowTypeIn: FALSE];
    END;

  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    i: INTEGER ← -1;
    nParams: CARDINAL = 12;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "Stats"L, proc: Stats, place: FormSW.newLine];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "Version"L, proc: Version];
    items[i ← i + 1] ← FormSW.StringItem[tag: "Target"L, string: @data.target, inHeap: TRUE];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "NameToAddress"L, proc: NameToAddress, place: FormSW.newLine];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "NameToCache"L, proc: NameToCache];
    items[i ← i + 1] ← FormSW.StringItem[tag: "Name"L, string: @data.name, inHeap: TRUE];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "AddressToName"L, proc: AddressToName, place: FormSW.newLine];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "AddressToCache"L, proc: AddressToCache];
    items[i ← i + 1] ← FormSW.StringItem[tag: "Address"L, string: @data.address, inHeap: TRUE];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "PupToTcp"L, proc: PupToTcp, place: FormSW.newLine];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "TcpToPup"L, proc: TcpToPup];
    items[i ← i + 1] ← FormSW.StringItem[tag: "Convert"L, string: @data.pupOrTcp, inHeap: TRUE];
    RETURN[items, TRUE];
    END;

  AlreadyActive: ERROR = CODE;
  NotActive: ERROR = CODE;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
        IF data # NIL THEN ERROR AlreadyActive;
        data ← z.NEW[Data];
        data↑ ← [
          where:, target: z.NEW[StringBody[20]], name: z.NEW[StringBody[20]],
          address: z.NEW[StringBody[20]], pupOrTcp: z.NEW[StringBody[20]] ];
        String.AppendString[data.target, "ME"L];
        [] ← PupDefs.PupPackageMake[];
        END;
      new = inactive =>
        BEGIN
        IF data = NIL THEN ERROR NotActive;
        PupDefs.PupPackageDestroy[];
        z.FREE[@data.target];
        z.FREE[@data.name];
        z.FREE[@data.address];
        z.FREE[@data];
        END;
      ENDCASE;
    END;

  -- Main Body
  Init[];
  END.