-- Pupwatch: communications and user interface

-- [Indigo]<Grapevine>PupWatch>Looker.mesa

-- Andrew Birrell  23-Nov-81 11:24:44 --

DIRECTORY
  -- Look on [ivy]<Juniper>n.n>User> for Pine bcds (currently n.n = 6.1).

Ascii		USING[ BS, ControlW, CR, DEL, ESC, SP ],
--CommonPineDefs	USING[DataRequest, Parameters],
DisplayDefs	USING[ DestroyDisplay ],
FrameDefs	USING[ IsBound ],
InlineDefs	USING[ BITAND, BITSHIFT, COPY, HighHalf, LowHalf ],
KeyDefs		USING[ Keys ],
LookerDefs	USING[ Clear, DiskChar, DiskCommit, DiskMultiple, DiskPos,
		       EraseChar, GetLength, Lookup, LookupOutcome,
		       SetPos, WriteChar, WriteMultiple, WriteTitle ],
MMOps		USING[ MakeBoot ],
Mopcodes	USING[ zLI2, zLI3, zMISC, zSTARTIO ],
OsStaticDefs	USING[ OsStatics ],
--PrivateCommonPineDefs USING[pLeader],
ProcessDefs	USING[ Abort, CV, InterruptLevel, SetPriority ],
PupDefs		USING[ PupAddress ],
PupTypes,
StreamDefs	USING[ GetDefaultKey, KeyboardHandle, StartKeyHandler ],
StringDefs,
SystemDefs	USING[ AllocateHeapNode, FreeHeapNode ];

Looker: MONITOR

IMPORTS DisplayDefs, InlineDefs, FrameDefs, LookerDefs, MMOps, ProcessDefs,
        StreamDefs, StringDefs, SystemDefs =

BEGIN

herald: STRING = "PupWatch: version of 24-Nov-81 14:49:32"L;

LongTicks: TYPE = LONG CARDINAL;

ClockFormat: TYPE = { AltoI, AltoII};

AltoClock: TYPE = MACHINE DEPENDENT RECORD[ -- hardware clock formats --
   SELECT OVERLAID ClockFormat FROM
      AltoI =>  [clock: CanonicalClock],
      AltoII => [junk1: [0..17B], low10: [0..1777B], junk2: [0..3B], high16: CARDINAL],
   ENDCASE ];

CanonicalClock: TYPE = MACHINE DEPENDENT RECORD[
   SELECT OVERLAID * FROM
      pieces => [low10: [0..1777B], zero: [0..77B], high16: CARDINAL],
      tick =>   [ticks: LongTicks]
   ENDCASE ];

longTicksPerSecond:  LongTicks = 6321200B -- (5.88*10↑6/224)SHL 6 --;
longTicksPerMSec:    LongTicks = longTicksPerSecond/1000;

ReadRawClock: PROC RETURNS[ AltoClock ] = MACHINE CODE
   { Mopcodes.zMISC, 11B; };

myClockFormat: ClockFormat;

MSec: PROC[rawClock: AltoClock] RETURNS[ LONG CARDINAL ] = INLINE
   BEGIN
   clock: CanonicalClock;
   WITH raw: rawClock SELECT myClockFormat FROM
      AltoI =>  clock ← raw.clock;
      AltoII => clock ← [pieces[low10:raw.low10, zero:, high16:raw.high16]];
   ENDCASE;
   clock.zero ← 0;
   RETURN[clock.ticks / longTicksPerMSec]
   END;


-- Ethernet driver --

HostNumber: TYPE = [0..256);

maxEtherLength: CARDINAL = 532 + 22 -- max Pup data + Pup header --;
-- Beware: ether packets with maxEtherLength+4 bytes can cause a parity
-- error at location 600B on Alto I's (microcode bug).

EtherBufferData: TYPE = MACHINE DEPENDENT RECORD[
   etherDest, etherSrce: HostNumber,
   etherType: {pup(1000B), ois(3000B), (177777B)},
   body: SELECT OVERLAID * FROM
     raw => [data:PACKED ARRAY [0..maxEtherLength) OF CHARACTER],
     pup => [pup: BufferData],
     ENDCASE ];

EtherBuffer: TYPE = POINTER TO EtherBufferData;

-- driver uses two ether buffers: one current, one standby --
etherBuffers: POINTER TO ARRAY [1..2] OF EtherBufferData =
                 SystemDefs.AllocateHeapNode[2*SIZE[EtherBufferData]];

Ethernet: TYPE = MACHINE DEPENDENT RECORD[
   status:        EtherStatus,
   interrupt:     WORD,
   endCount:      CARDINAL,
   loadMask:      WORD,
   inputCount:    CARDINAL,
   inputPointer:  EtherBuffer,
   outputCount:   CARDINAL, 
   outputPointer: EtherBuffer,
   spare2:        [0..256),
   hostNumber:    HostNumber ];

EtherStatus: TYPE = MACHINE DEPENDENT RECORD[
   micro: { inputDone(0), outputDone(1), overRun(2), overLoad(3),
            zeroLength(4), reset(5), microError(6), unset(255) },
   spare0, spare1, dataLate, collision, crc, input, output, incomplete:
      { yes(0), no(1) } ];

notPosted: EtherStatus = [unset,,,,,,,,];
inputOK:   EtherStatus = [inputDone,no,no,no,no,no,no,no,no];

ethernet: POINTER TO Ethernet = LOOPHOLE[600B];
etherCond: CONDITION; -- for naked notify --

StartInput: PROC = MACHINE CODE
   { Mopcodes.zLI2; Mopcodes.zSTARTIO };

Reset: PROC = MACHINE CODE
   { Mopcodes.zLI3; Mopcodes.zSTARTIO };


-- Pup level --

maxPupLength: CARDINAL = 54;

BufferData: TYPE = MACHINE DEPENDENT RECORD[
   pupLength: CARDINAL,
   pupTransportControl: [0..256),
   pupType: PupTypes.PupType,
   pupID: PupTypes.Pair,
   dest: PupDefs.PupAddress,
   source: PupDefs.PupAddress,
   pupBody: SELECT OVERLAID * FROM
     pupChars => [pupChars: PACKED ARRAY[0..maxPupLength) OF CHARACTER],
     pupBytes => [pupBytes: PACKED ARRAY[0..maxPupLength) OF [0..377B]],
     pupWords => [pupWords: ARRAY[0..maxPupLength/2) OF CARDINAL],
     pupString => [pupString: StringBody],
     rfc =>   [address: PupDefs.PupAddress ],
     ack =>   [maximumBytesPerPup, numberOfPupsAhead,
               numberOfBytesAhead: CARDINAL ],
     abort => [abortCode: CARDINAL,
               abortText: PACKED ARRAY [0..0) OF CHARACTER ],
     error => [errorHeader: ARRAY [0..9] OF WORD,
               errorCode: CARDINAL,
               errorOptions: WORD,
               errorText: PACKED ARRAY [0..0) OF CHARACTER],
     nameIs => [nameIs: ARRAY [0..0) OF PupDefs.PupAddress ],
     ENDCASE ];

Buffer: TYPE = POINTER TO BufferData;

ContentsBytes: PROC[b: Buffer] RETURNS[CARDINAL] = INLINE
   { RETURN[ b.pupLength - 22 ] };

nBuffers: CARDINAL = 300;
BufferIndex: TYPE = [0..nBuffers);

buffers: POINTER TO ARRAY BufferIndex OF BufferData =
            SystemDefs.AllocateHeapNode[nBuffers * SIZE[BufferData]];

times: POINTER TO ARRAY BufferIndex OF AltoClock =
            SystemDefs.AllocateHeapNode[nBuffers * SIZE[AltoClock]];

lost: PACKED ARRAY BufferIndex OF BOOLEAN ← ALL[FALSE];
logged: PACKED ARRAY BufferIndex OF BOOLEAN ← ALL[FALSE];

wBuffer: BufferIndex ← 0; -- buffer being written by ethernet driver --
rBuffer: BufferIndex ← 0; -- buffer Looker wants to read --
fullBuffers: [0..nBuffers] ← 0;
bufferChange: CONDITION;
driverWaiting: BOOLEAN ← FALSE;
lookerWaiting: BOOLEAN ← FALSE;
allUsed: BOOLEAN ← FALSE; -- whether all buffers have been written --

GetBuffer: ENTRY PROC RETURNS[BufferIndex] = --INLINE--
   BEGIN
   ENABLE UNWIND => lookerWaiting ← FALSE;
   WHILE fullBuffers = 0
   DO lookerWaiting ← TRUE; WAIT bufferChange ENDLOOP;
   lookerWaiting ← FALSE;
   RETURN[ rBuffer ]
   END;

ReturnBuffer: ENTRY PROC = --INLINE--
   { rBuffer ← IF rBuffer = LAST[BufferIndex] THEN 0 ELSE SUCC[rBuffer];
     fullBuffers ← fullBuffers-1;
     IF driverWaiting THEN NOTIFY bufferChange };

ReplayBuffers: ENTRY PROC =
   BEGIN
   rBuffer ← IF allUsed
             THEN IF wBuffer = LAST[BufferIndex] THEN 0 ELSE SUCC[wBuffer]
             ELSE 0;
   fullBuffers ← (wBuffer+nBuffers-rBuffer) MOD nBuffers;
   IF lookerWaiting THEN NOTIFY bufferChange;
   END;

FlushBuffers: ENTRY PROC =
   BEGIN
   rBuffer ← wBuffer ← 0;
   fullBuffers ← 0; allUsed ← FALSE; lost ← ALL[FALSE]; logged ← ALL[FALSE];
   IF driverWaiting THEN NOTIFY bufferChange;
   END;

NextBuffer: INTERNAL PROC = --INLINE--
   BEGIN
   ENABLE UNWIND => driverWaiting ← FALSE;
   IF wBuffer = LAST[BufferIndex]
   THEN { allUsed ← TRUE; wBuffer ← 0 }
   ELSE wBuffer ← SUCC[wBuffer];
   IF lookerWaiting THEN NOTIFY bufferChange;
   logged[wBuffer] ← lost[wBuffer] ← FALSE;
   -- don't overwrite nBuffers/2 old ones, to help "replay" facility. --
   IF fullBuffers >= nBuffers/2
   THEN WHILE fullBuffers+50 >= nBuffers/2 --make sure we have 50 free--
        DO driverWaiting←TRUE; lost[wBuffer] ← TRUE;
           WAIT bufferChange
        ENDLOOP;
   fullBuffers ← fullBuffers+1;
   driverWaiting ← FALSE;
   END;

wanted: PupDefs.PupAddress ← [ [0], [0], [0,0] ];

Driver: ENTRY PROC =
   BEGIN
   ProcessDefs.SetPriority[4];
   ethernet.inputCount ← SIZE[EtherBufferData];
   ethernet.inputPointer ← @(etherBuffers[1]);
   ethernet.status ← notPosted;
   StartInput[];
   DO ENABLE ABORTED => EXIT;
      current: EtherBuffer = ethernet.inputPointer;
      next:    EtherBuffer = IF current = @(etherBuffers[1])
                             THEN @(etherBuffers[2])
                             ELSE @(etherBuffers[1]);
      thisStatus: EtherStatus;
      DO WAIT etherCond;
         IF (thisStatus ← ethernet.status) # notPosted
         THEN BEGIN
              -- restart the microcode as soon as possible --
              ethernet.inputPointer ← next;
              ethernet.status ← notPosted;
              StartInput[];
              EXIT
              END;
      ENDLOOP;
      IF thisStatus = inputOK
      AND current.etherType = pup
      AND (  (current.pup.dest.host = wanted.host
              AND current.pup.dest.net = wanted.net)
          OR (current.pup.source.host = wanted.host
              AND current.pup.source.net = wanted.net)
          )
      THEN BEGIN
           myBuffer: BufferIndex = wBuffer;
           times[myBuffer] ← ReadRawClock[];
           InlineDefs.COPY[from: @(current.pup), to: @(buffers[myBuffer]),
                           nwords: SIZE[BufferData] ];
           NextBuffer[];
           END;
   ENDLOOP;
   Reset[];
   ProcessDefs.SetPriority[1];
   END;



driverProcess: PROCESS;

NoInterruptLevel: ERROR = CODE;
etherLevel: ProcessDefs.InterruptLevel;

TakeEthernet: PROC =
   BEGIN
   ethernet.hostNumber ← 0 --promiscuous--;
   FOR i: ProcessDefs.InterruptLevel IN ProcessDefs.InterruptLevel
   DO IF ProcessDefs.CV[i] = NIL THEN { etherLevel ← i; EXIT };
   REPEAT
   FINISHED => ERROR NoInterruptLevel[];
   ENDLOOP;
   ProcessDefs.CV[etherLevel] ← @etherCond;
   ethernet.interrupt ← InlineDefs.BITSHIFT[1, etherLevel];
   Reset[]; Reset[]; -- Hal says it sometimes doesn't work! --
   driverProcess ← FORK Driver[];
   END;

GiveEthernet: PROC =
   BEGIN
   ProcessDefs.Abort[driverProcess];
   JOIN driverProcess;
   ethernet.interrupt ← 0;
   ProcessDefs.CV[etherLevel] ← NIL;
   Reset[];
   END;



-- Synchronization with user type-in --

inputActive:  BOOLEAN ← TRUE;
lookerActive: BOOLEAN ← FALSE;
pauseWanted:  BOOLEAN ← FALSE;
inputChange:  CONDITION;
lookerChange: CONDITION;

ActivateLooker: ENTRY PROC RETURNS[ BOOLEAN ] = --INLINE--
   BEGIN
   ENABLE UNWIND => NULL;
   IF inputActive OR pauseWanted
   THEN BEGIN
        WHILE inputActive OR pauseWanted DO WAIT inputChange ENDLOOP;
        RETURN[FALSE]
        END
   ELSE BEGIN
        lookerActive ← TRUE; RETURN[TRUE]
        END;
   END;

DeactivateLooker: ENTRY PROC = INLINE
   { lookerActive ← FALSE; IF inputActive THEN NOTIFY lookerChange };

ActivateInput: ENTRY PROC =
   BEGIN
   ENABLE UNWIND => NULL;
   inputActive ← TRUE;
   WHILE lookerActive DO WAIT lookerChange ENDLOOP;
   END;

DeactivateInput: ENTRY PROC =
   BEGIN
   inputActive ← FALSE; NOTIFY inputChange;
   END;


-- Output subroutines --

mode: { display, disk} ← display;

-- Display line layout is:
-- 7777: from 77#377#777 [aData,L:777,to:177777]abcdefghijklmn
-- 7777:   to 77#377#777 [aData,L:777,to:177777]abcdefghijklmn
tabFrom:  CARDINAL;
tabTo:    CARDINAL;
tabAddr:  CARDINAL;
tabPkt:   CARDINAL;
tabData:  CARDINAL;

-- Disk log line layout is:
-- 7777: from 377#377#377 [aData,L:1024,to:177777] abcdefghijklmn
-- 7777:   to 377#377#377 [aData,L:1024,to:177777] abcdefghijklmn
diskFrom: CARDINAL;
diskPkt:  CARDINAL;
diskData: CARDINAL;

InitTabs: PROC =
  BEGIN
  tabFrom  ← LookerDefs.GetLength["7777: "L];
  tabTo    ← tabFrom +
              (LookerDefs.GetLength["from"L]-LookerDefs.GetLength["to"L]);
  tabAddr  ← tabFrom + LookerDefs.GetLength["from "L];
  tabPkt   ← tabAddr + LookerDefs.GetLength["77#777#777 "L];
  tabData  ← tabPkt + LookerDefs.GetLength["[aData,L:777,to:177777]"L];
  diskFrom ← 6;             -- "7777: " --
  diskPkt  ← diskFrom + 17; -- "from 377#377#377 " --
  diskData ← diskPkt + 25;  -- "[aData,L:1024,to:177777] " --
  END;

WriteChar: PROC[CHARACTER] ← LookerDefs.WriteChar;

WriteMultiple: PROC[DESCRIPTOR FOR PACKED ARRAY OF CHARACTER] ←
  LookerDefs.WriteMultiple;

WriteString: PROC[s: STRING] =
   { WriteMultiple[DESCRIPTOR[@(s.text),s.length]] };

WritePup: PROC[b: Buffer] = INLINE
   { WriteMultiple[DESCRIPTOR[@(b.pupChars),
                   MIN[ContentsBytes[b],maxPupLength]]] };

WriteOctal777: PROC[n: CARDINAL] =
   BEGIN
   IF n >= 10B
   THEN BEGIN
        IF n >= 100B
        THEN WriteChar['0 + n / 100B];
        WriteChar['0 + (n/10B) MOD 10B];
        END;
   WriteChar['0 + n MOD 10B];
   END;

WriteOctal: PROC[n: CARDINAL] = INLINE
   BEGIN
   IF n < 1000B
   THEN WriteOctal777[n]
   ELSE WriteFullOctal[n];
   END;

WriteFullOctal: PROC[n: CARDINAL] =
   BEGIN
   WriteOctal777[n/1000B];
   WriteChar['0 + ((n/100B) MOD 10B)];
   WriteChar['0 + ((n/10B) MOD 10B)];
   WriteChar['0 + n MOD 10B];
   END; 

WriteLongOctal: PROC[n: LONG CARDINAL]  = INLINE
   BEGIN
   IF InlineDefs.HighHalf[n] # 0 THEN WriteOctal[InlineDefs.HighHalf[n]];
   WriteOctal[InlineDefs.LowHalf[n]];
   END;

WritePair: PROC[p: PupTypes.Pair] =
   { WriteOctal[p.a]; WriteChar[',]; WriteOctal[p.b] };

WriteDecimal: PROC[n: CARDINAL] = INLINE
   { s: STRING = [5]; StringDefs.AppendDecimal[s,n]; WriteString[s] };
 
WriteAddr: PROC[addr: PupDefs.PupAddress] = INLINE
   BEGIN
   WriteOctal777[addr.net];
   WriteChar['#];
   WriteOctal777[addr.host];
   WriteChar['#];
   WriteOctal777[InlineDefs.BITAND[addr.socket.b,777B]];
   END;

WriteID: PROC[n: CARDINAL] = INLINE
   BEGIN
   WriteString["to:"];
   WriteOctal[n];
   END;

prevMS: LONG CARDINAL ← MSec[ReadRawClock[]];

-- layout of Pup id's for Sequin/Leaf protocol --

SequinID: TYPE = MACHINE DEPENDENT RECORD[
   allocate: Byte, receiveSequence: Byte,
   control: SequinControl, sendSequence: Byte ];

Byte: TYPE = [0..256);

SequinControl: TYPE = MACHINE DEPENDENT {
  data(0), ack(1), nop(2), restart(3),
  check(4), open(5), break(6), close(7),
  closed(8), destroy(9), dallying(10), quit(11),
  broken(12), retransmit(13), stifle(14), openclose(15),
  opendestroy(16), (255) };

leafSocket: PupTypes.PupSocketID = [a:0,b:43B];


Watch: PROC[ b: Buffer, time: AltoClock ] =
   BEGIN
   DefaultBodyPrintout: PROC[b: Buffer] = INLINE
      { WriteString["L:"]; WriteOctal[ContentsBytes[b]] };
   BEGIN
        mscSrv: BOOLEAN = b.source.socket = PupTypes.miscSrvSoc
                          OR b.dest.socket = PupTypes.miscSrvSoc;
        leaf: BOOLEAN = b.source.socket = leafSocket
                        OR b.dest.socket = leafSocket;
        newMS: LONG CARDINAL = MSec[time];
        IF newMS >= prevMS + 10000
        THEN IF newMS >= prevMS + 1000*LONG[1000] -- 1000 seconds --
             THEN WriteString[IF prevMS = 0 THEN "first"L ELSE "long"L]
             ELSE BEGIN
                  WriteDecimal[InlineDefs.LowHalf[(newMS-prevMS)/1000]];
                  WriteChar['s];
                  END
        ELSE WriteDecimal[InlineDefs.LowHalf[ (newMS-prevMS) MOD 10000 ]];
        prevMS ← newMS;
        WriteChar[':];
        IF mode = display
        THEN BEGIN
             IF b.source.host = wanted.host
             AND b.source.net = wanted.net
             THEN { LookerDefs.SetPos[tabTo]; WriteString["to"];
                    LookerDefs.SetPos[tabAddr]; WriteAddr[b.dest] }
             ELSE { LookerDefs.SetPos[tabFrom]; WriteString["from"];
                    LookerDefs.SetPos[tabAddr]; WriteAddr[b.source] };
             LookerDefs.SetPos[tabPkt];
             END
        ELSE BEGIN
             LookerDefs.DiskPos[diskFrom];
             IF b.source.host = wanted.host
             AND b.source.net = wanted.net
             THEN { WriteString["  to "]; WriteAddr[b.dest] }
             ELSE { WriteString["from "]; WriteAddr[b.source] };
             LookerDefs.DiskPos[diskPkt];
             END;
        WriteChar['[];
        BEGIN
          type: STRING = SELECT b.pupType FROM

              -- registered pup types
              echoMe =>  "echoMe",
              iAmEcho => "iAmEcho",
              badEcho => "badEcho",
              error =>   "error",
              rfc =>     "rfc",
              abort =>   "abort",
              end =>     "end",
              endRep =>  "endRep",
              data =>    "data",
              aData =>   "aData",
              ack =>     "ack",
              mark =>    "mark",
              int =>     "int",
              intRep =>  "intRep",
              aMark =>   "aMark",
              eData =>   "eData",
              eAck =>    "eAck",
              eEnd =>    "eEnd",
              eAbort =>  "eAbort",
              rpp =>     "rpp",
              -- Cedar RPC pup types.  Bits are:
              -- 8..10: 3 (=> start at pt140)
              -- 11: {end(0),notEnd(1)}
              -- 12: {dontAck(0),pleaseAck(1)}
              -- 13..15: {call,data,ack,nak,rfa}
              pt140 =>   "RPC-call-end",
              pt141 =>   "RPC-data-end",
              pt142 =>   "RPC-ack",
              pt143 =>   "RPC-nak",
              pt144 =>   "RPC-RFA",
              pt150 =>   "RPC-a-call-end",
              pt151 =>   "RPC-a-data-end",
              pt152 =>   "RPC-ping",
              pt153 =>   "RPC-a-nak",
              pt154 =>   "RPC-a-RFA",
              pt160 =>   "RPC-call-more",
              pt161 =>   "RPC-data-more",
              pt170 =>   "RPC-a-call-more",
              pt171 =>   "RPC-a-data-more",

              -- unregistered pup types (possible overlap)
              gatewayRequest =>   "gatewayRequest",
              --also dateTextRequest (socket 4), statisticsRequest (socket 22)
              gatewayInfo =>      "gatewayInfo",
              --also dateTextIs (socket 4), statisticsAre (socket 22)
              dateTenexRequest => "dateTenexRequest",
              dateTenexIs =>      "dateTenexIs",
              dateAltoRequest =>  "dateAltoRequest",
              dateAltoIs =>       "dateAltoIs",
              mailCheck =>        "mailCheck",
              mailIsNew =>        "mailIsNew",
              mailNotNew =>       "mailNotNew",
              mailError =>        "mailError",
              mailCheckLaurel =>  "mailCheckL",
              nameLookup =>       "nameLookup",
              nameIs =>           "nameIs",
              nameError =>        "nameError",
              addressLookup =>    "addrLookup",
              addressIs =>        "addrIs",
              whereIsUser =>      "whereIsUser",
              userIs =>           "userIs",
              userError =>        "userError",
              netDirVersion =>    "netDirVersion",
              --also eventReport (socket 30)
              sendNetDir =>       "sendNetDir",
              --also eventReportReply (socket 30)
              bootFileSend =>     "bootFileSend",
              kissOfDeath =>      "kissOfDeath",
              bootDirReq =>       "bootDirReq",
              bootDirReply =>
                IF NOT leaf THEN "bootDirReply"
                ELSE SELECT LOOPHOLE[b.pupID,SequinID].control FROM
                     data=>  "lData",          ack=>        "lAck",
                     nop=>   "nop",            restart=>    "restart",
                     check=> "check",          open=>       "open",
                     break=> "break",          close=>      "close",
                     closed=>"closed",         destroy=>    "destroy",
                     dallying=>"dallying",     quit=>       "quit",
                     broken=>"broken",         retransmit=> "retransmit",
                     stifle=>"stifle",         openclose=>  "openClose",
                     opendestroy=>"opendestroy",
                     ENDCASE => "leaf?",
              -- Pine pup types
              request =>          IF mscSrv THEN "userAuthReq"
                                  ELSE "request",
              result =>           IF mscSrv THEN "userAuthOk"
                                  ELSE "result",
              unsolicited =>      IF mscSrv THEN "userAuthBad"
                                  ELSE "unsolicited",
              custodian =>        "custodian",
              sync =>             "sync",
              pineAck =>          "pineAck",
              noop =>             "noop",
            ENDCASE => NIL;
          IF type = NIL
          THEN BEGIN
               WriteString["pt"];
               WriteOctal[LOOPHOLE[b.pupType, CARDINAL]];
               END
          ELSE WriteString[type];
        END;

        WriteChar[',];

        SELECT b.pupType FROM
          error =>
            SELECT b.errorCode FROM
              PupTypes.badChecksumPupErrorCode =>
                WriteString["badChecksum"];
              PupTypes.noProcessPupErrorCode =>
                WriteString["noSuchPort"];
              PupTypes.resourceLimitsPupErrorCode =>
                WriteString["resourceLimits"];
              PupTypes.inconsistentPupErrorCode =>
                WriteString["inconsistentPup"];
              PupTypes.cantGetTherePupErrorCode =>
                WriteString["cantGetThere"];
              PupTypes.hostDownPupErrorCode =>
                WriteString["hostDown"];
              PupTypes.eightHopsPupErrorCode =>
                WriteString["sixteenHops"];
              PupTypes.tooBigPupErrorCode =>
                WriteString["tooBigPup"];
              PupTypes.iAmNotAGatewayPupErrorCode =>
                WriteString["iAmNotAGateway"];
              PupTypes.gatewayResourceLimitsPupErrorCode =>
                WriteString["gatewayResources"];
            ENDCASE => WriteOctal[b.errorCode];
          mailCheck, mailCheckLaurel, nameLookup =>
            WritePup[b];
          nameIs =>
            WriteAddr[b.nameIs[0]];
          abort =>
            WriteOctal[b.abortCode];
          data, aData =>
            BEGIN
            l: CARDINAL = ContentsBytes[b];
            WriteID[b.pupID.b+l];
            WriteString[",L:"];
            WriteOctal[l];
            END;
          ack =>
            BEGIN
            WriteID[b.pupID.b];
            WriteString[",pups:"];
            WriteOctal777[b.numberOfPupsAhead];
            END;
          mark, aMark =>
            BEGIN
            WriteID[b.pupID.b+1];
            WriteString[",mk:"];
            WriteOctal777[b.pupBytes[0]];
            END;
          rfc, echoMe =>
            BEGIN
            wellKnown: STRING = SELECT b.dest.socket FROM
                PupTypes.telnetSoc =>      "telnet",
                PupTypes.gatewaySoc =>     "gateway",
                PupTypes.ftpSoc =>         "ftp",
                PupTypes.miscSrvSoc =>     "miscSrv",
                PupTypes.echoSoc =>        "echo",
                PupTypes.bspTestSoc =>     "bspTest",
                PupTypes.mailSoc =>        "mail",
                PupTypes.eftpReceiveSoc => "eftp",
                PupTypes.copyDiskSoc =>    "copyDisk",
                PupTypes.rpcpSoc =>        "rpcp",
                PupTypes.librarianSoc =>   "librarian",
                PupTypes.pineSoc =>        "pine",
              ENDCASE => NIL;
            IF wellKnown # NIL
            THEN WriteString[wellKnown]
            ELSE BEGIN
                 IF b.dest.socket.b IN [50B..57B]
                 AND b.dest.socket.a IN [0..1]
                 THEN BEGIN -- RFC or EchoMe on a Grapevine socket --
                      gvSoc: STRING = SELECT b.dest.socket.b FROM
                          50B => "RS-enquire",
                          51B => "RS-update",
                          52B => "RS-poll",
                          53B => "GV-Lily",
                          54B => "MS-poll",
                          55B => "MS-forward",
                          56B => "MS-send",
                          57B => "MS-retrieve",
                        ENDCASE => ERROR;
                      IF b.dest.socket.a = 1 THEN WriteString["Test-"];
                      WriteString[gvSoc];
                      END
                 ELSE IF b.dest.socket.a = 0
                      AND b.dest.socket.b IN [0 .. 256)
                      THEN WriteOctal777[b.dest.socket.b];
                 END;
            END;
          end, endRep, int, intRep => NULL;
          pt140,pt141,pt142,pt143,pt144,
          pt150,pt151,pt152,pt153,pt154,
          pt160,pt161,
          pt170,pt171 => --Cedar RPC packet--
            BEGIN
            l: CARDINAL = ContentsBytes[b];
            WriteString["W:"];
            IF l < 20
            THEN { WriteString["?-"L]; WriteOctal[10-l/2] }
            ELSE WriteOctal[l/2-10];
            END;
          addressLookup =>
            WriteAddr[b.nameIs[0]];
          bootDirReply =>
            IF leaf
            THEN BEGIN
                 id: SequinID = LOOPHOLE[b.pupID];
                 IF b.dest.socket = leafSocket
                 THEN BEGIN
                      WriteString["rec:"];
                      WriteOctal777[id.receiveSequence];
                      WriteString[",send:"];
                      WriteOctal777[id.sendSequence];
                      END
                 ELSE BEGIN
                      WriteString["send:"];
                      WriteOctal777[id.sendSequence];
                      WriteString[",rec:"];
                      WriteOctal777[id.receiveSequence];
                      END;
--                 WriteString[",alloc:"];
--                 WriteOctal777[id.allocate];
                 IF id.control = data
                 THEN BEGIN
                      l: CARDINAL = ContentsBytes[b];
                      WriteString[",L:"];
                      WriteOctal[l];
                      END;
                 END;
          request =>
            IF mscSrv THEN WriteString[@(b.pupString)] --userAuthReq--
            ELSE {
-- Pine stuff commented out to save space - otherwise the compiler runs out
--              Wr: PROC[s: STRING] = { WriteString[s] };
--              L: PrivateCommonPineDefs.pLeader = LOOPHOLE[@b.pupBody];
--              P: POINTER TO CommonPineDefs.Parameters = LOOPHOLE[@L.params];
--	    WITH R: P.requestP SELECT FROM
--	      ReadPage => { Wr["ReadPage "]; WriteOctal[R.pageNumber] };
--	      WritePage => { Wr["WritePage "]; WriteOctal[R.pageNumber] };
--	      SetLength => { Wr["SetLength "]; WriteLongOctal[R.byteCount] };
--	      ReadLength => Wr["ReadLength"];
--	      CloseFile => Wr["CloseFile"];
--	      DestroyAnonymousFile => Wr["DestroyAnonymousFile"];
--	      ReadData => { Wr["ReadData "]; WriteLongOctal[R.firstBytePosition];
--	        WriteString[" for "]; WriteOctal[R.byteCount] };
--	      WriteData => { Wr["WriteData "]; WriteLongOctal[R.firstBytePosition];
--	        WriteString[" for "]; WriteOctal[R.byteCount] };
--	      ReadAttribute => { Wr["ReadAttribute "]; WriteOctal[R.attributeNumber] };
--	      WriteAttribute => { Wr["WriteAttribute "]; WriteOctal[R.attributeNumber] };
--	      SetReadLock => Wr["SetReadLock"];
--	      SetWriteLock => Wr["SetWriteLock"]; 
--	      ReleaseReadLock => Wr["ReleaseReadLock"];
--	      ObtainCapability => Wr["ObtainCapability"];
--	      Room => Wr["Room"];
--	      ChangePassword => Wr["ChangePassword"];
--	      OpenFile => Wr["OpenFile"];
--	      CreateAnonymousFile => Wr["CreateAnonymousFile"];
--	      FindFile => Wr["FindFile"];
--	      LockQuery => Wr["LockQuery"];
--	      TransCompletionQuery => Wr["TransCompletionQuery"];
--	      OpenFileFromCapability => Wr["OpenFileFromCapability"];
--	      Logout => Wr["Logout"];
--	      CloseTransaction => Wr["CloseTransaction"];
--	      AbortTransaction => Wr["AbortTransaction"];
--	      LookupFile => Wr["LookupFile"];
--	      CreateFile => Wr["CreateFile"];
--	      DestroyFile => Wr["DestroyFile"];
--	      RenameFile => Wr["RenameFile"];
--	      NextFile => Wr["NextFile"];
--	      NextFewFiles => Wr["NextFewFiles"];
--	      ValidateYourLocks => Wr["ValidateYourLocks"];
--	      ReleaseYourReadLocks => Wr["ReleaseYourReadLocks"];
--	      ENDCASE => Wr["Unknown Request Variant"];
--	    IF P.requestP.code IN CommonPineDefs.DataRequest THEN {
--	      Wr[" sfh "]; WriteOctal[P.h] };
            };
          result =>
            IF mscSrv THEN DefaultBodyPrintout[b] --userAuthOk--
            ELSE {
-- More Pine stuff commented out ...
--              Wr: PROC[s: STRING] = { WriteString[s] };
--              L: PrivateCommonPineDefs.pLeader = LOOPHOLE[@b.pupBody];
--              P: POINTER TO CommonPineDefs.Parameters = LOOPHOLE[@L.params];
--	    WITH R: P.resultP SELECT FROM
--	      HeresData => { Wr["HeresData "]; WriteOctal[R.length] };
--	      HeresLFH => Wr["HeresLFH"];
--	      HeresEntry => Wr["HeresEntry"];
--	      HeresFileList => { Wr["HeresFileList "]; WriteOctal[R.count] };
--	      HeresFile => { Wr["HeresFile "]; WriteOctal[R.f] };
--	      HeresLength => { Wr["HeresLength "]; WriteLongOctal[R.length] };
--	      CommandAck => Wr["CommandAck"];
--	      CommandNak => { Wr["CommandNak "]; WriteOctal[LOOPHOLE[R.en]] };
--	      LoginResponse => Wr["LoginResponse"];
--	      TransactionClosed => Wr["TransactionClosed"];
--	      LogoutResponse => Wr["LogoutResponse"];
--	      ResourceData => Wr["ResourceData"];
--	      TransCompletionInfo => Wr["TransCompletionInfo"];
--	      HeresRoom => Wr["HeresRoom"];
--	      LockQueryResponse => Wr["LockQueryResponse"];
--	      ENDCASE => Wr["Unknown Result Variant"];
            };
          unsolicited =>
            IF mscSrv THEN DefaultBodyPrintout[b] --userAuthBad--
            ELSE {
-- More Pine stuff commented out ...
--              L: PrivateCommonPineDefs.pLeader = LOOPHOLE[@b.pupBody];
--              P: POINTER TO CommonPineDefs.Parameters = LOOPHOLE[@L.params];
--	    WITH R: P.unsolicitedP SELECT FROM
--	      TransactionAborted => WriteString["TransactionAborted"];
--	      ReadLockBroken => WriteString["ReadLockBroken"];
--	      ENDCASE => WriteString["Unknown Unsolicited Variant"];
            };
          custodian => {
-- More Pine stuff commented out ...
--            L: PrivateCommonPineDefs.pLeader = LOOPHOLE[@b.pupBody];
--            P: POINTER TO CommonPineDefs.Parameters = LOOPHOLE[@L.params];
--	  WITH R: P.custodianP SELECT FROM
--	    Login => WriteString["Login"];
--	    AddServer => WriteString["AddServer"];
--	    ResourceLocation => WriteString["ResourceLocation"];
--	    ENDCASE => WriteString["Unknown Custodian Variant"];
            };
          sync => NULL;
          pineAck => NULL;
          noop => NULL;
        ENDCASE => DefaultBodyPrintout[b];
        WriteChar[']];
        IF mode = disk
        THEN BEGIN
             LookerDefs.DiskPos[diskData];
             WriteString["ID="];
             WritePair[b.pupID];
             WriteString[" from="];
             WritePair[LOOPHOLE[b.source.socket]];
             WriteString[" to="];
             WritePair[LOOPHOLE[b.dest.socket]];
             BEGIN
               l: CARDINAL = MIN[ContentsBytes[b],maxPupLength];
               WriteChar[Ascii.CR];
               LookerDefs.DiskPos[diskData];
               IF l = 0 THEN WriteString["{empty}"] ELSE WritePup[b];
               WriteChar[Ascii.CR];
               LookerDefs.DiskPos[diskData];
               IF l = 0
               THEN WriteString["{empty}"]
               ELSE FOR i: CARDINAL IN [0..MIN[l,maxPupLength/2])
                    DO WriteOctal777[b.pupBytes[i]];
                       WriteChar[Ascii.SP]
                    ENDLOOP;
             END;
             END
        ELSE IF b.pupType = data OR b.pupType = aData
             THEN { LookerDefs.SetPos[tabData]; WritePup[b] };
        WriteChar[Ascii.CR];
   END;
   END;


speed: {slow, fast} ← fast;
lookerCount: CARDINAL;

LookerMain: PROC =
   BEGIN
   countLimit: CARDINAL = 40;
   ProcessDefs.SetPriority[0];
   DO ENABLE ABORTED => EXIT;
      this: BufferIndex = GetBuffer[];
      IF ActivateLooker[]
      THEN BEGIN
           IF lost[this]
           THEN { WriteString["Lost packet(s)"L]; WriteChar[Ascii.CR] };
           Watch[@(buffers[this]), times[this]];
           ReturnBuffer[];
           IF speed = slow
           AND (lookerCount ← lookerCount + 1) >= countLimit
           THEN { WriteString["Type <SP> to continue ..."L];
                  pauseWanted ← TRUE };
           DeactivateLooker[];
           END;
   ENDLOOP;
   GiveEthernet[];
   END;

WriteDiskLog: PROC =
   BEGIN
   WriteChar ← LookerDefs.DiskChar;
   WriteMultiple ← LookerDefs.DiskMultiple;
   mode ← disk;
   WriteChar[Ascii.CR];
   ReplayBuffers[];
   IF logged[GetBuffer[]]
   THEN BEGIN
        WriteString["{ continued from previous logged packets }"L];
        WriteChar[Ascii.CR];
        THROUGH [1..fullBuffers]
        DO this: BufferIndex = GetBuffer[];
           IF logged[this]
           THEN prevMS ← MSec[times[this]]
           ELSE EXIT;
           ReturnBuffer[];
        ENDLOOP;
        END
   ELSE BEGIN
        WriteString["Watching host "L];
        WriteOctal777[wanted.net]; WriteChar['#];
        WriteOctal777[wanted.host]; WriteChar['#];
        WriteChar[Ascii.CR];
        END;
   THROUGH [1..fullBuffers]
   DO this: BufferIndex = GetBuffer[];
      IF lost[this]
      THEN { WriteString["Lost packet(s)"L]; WriteChar[Ascii.CR] };
      Watch[@(buffers[this]), times[this]];
      logged[this] ← TRUE;
      ReturnBuffer[];
   ENDLOOP;
   WriteChar ← LookerDefs.WriteChar;
   WriteMultiple ← LookerDefs.WriteMultiple;
   mode ← display;
   LookerDefs.DiskCommit[];
   END;



-- user command input --

WriteTitle: PROC =
   BEGIN
   myTitle: STRING = [50];
   StringDefs.AppendString[myTitle, "PUPWatch: watching host "L];
   StringDefs.AppendNumber[myTitle, wanted.net, 8];
   StringDefs.AppendChar[myTitle, '#];
   StringDefs.AppendNumber[myTitle, wanted.host, 8];
   StringDefs.AppendChar[myTitle, '#];
   LookerDefs.WriteTitle[myTitle];
   END;

HandleInput: PROC =
   BEGIN
   c: CHARACTER ← 'h;
   DO alreadyPaused: BOOLEAN = pauseWanted;
      IF pauseWanted AND c # Ascii.SP THEN WriteChar[Ascii.CR];
      lookerCount ← 0;
      SELECT c FROM
     'q, 'Q => { WriteString["Quit"]; EXIT };
     'f, 'F =>
       BEGIN
       WriteString["Fast"L];
       speed ← fast; pauseWanted ← FALSE;
       END;
     'h, 'H =>
       BEGIN
       s: STRING = [40];
       WriteString["Host (NLS-name or Net-address): "];
       DO c: CHARACTER = key.get[key];
          SELECT c FROM
           Ascii.BS =>
             BEGIN
             IF s.length > 0
             THEN { s.length ← s.length-1;
                    LookerDefs.EraseChar[s[s.length]] };
             LOOP
             END;
           Ascii.ControlW =>
             BEGIN
             FOR i: CARDINAL DECREASING IN [0..s.length)
             DO LookerDefs.EraseChar[s[i]] ENDLOOP;
             s.length ← 0;
             LOOP
             END;
           Ascii.DEL => GOTO del;
           Ascii.SP, Ascii.CR, Ascii.ESC => EXIT;
          ENDCASE => NULL;
          IF s.length < s.maxlength
          THEN { StringDefs.AppendChar[s, c]; WriteChar[c] };
       ENDLOOP;
       WriteString[" ... "];
       GiveEthernet[]; -- stops driver --
       BEGIN
          outcome: LookerDefs.LookupOutcome;
          net, host: [0..255];
          [outcome, net, host] ← LookerDefs.Lookup[s];
          SELECT outcome FROM
            ok =>
              BEGIN
              WriteString["ok"L];
              wanted.net ← [net]; wanted.host ← [host];
              LookerDefs.Clear[];
              FlushBuffers[];
              WriteTitle[];
              pauseWanted ← FALSE;
              END;
            noResponse => WriteString["no name-lookup response"L];
            badName => WriteString["name not found"L];
          ENDCASE => ERROR;
       END;
       TakeEthernet[]; -- starts driver --
       EXITS
         del => WriteString[" XXX"L];
       END;
     'r, 'R =>
       BEGIN
       LookerDefs.Clear[];
       WriteString["Replay (Slow)"L];
       ReplayBuffers[]; speed ← slow;
       pauseWanted ← FALSE;
       END;
     's, 'S =>
       BEGIN
       WriteString["Slow"L];
       speed ← slow;
       pauseWanted ← FALSE;
       END;
     'w, 'W =>
       IF version = boot
       THEN WriteString["""Write log"" not available in Pupwatch.boot"L]
       ELSE BEGIN
            WriteString["Writing Pupwatch.log ... "L];
            WriteDiskLog[];
            WriteString["ok"L];
            END;
     Ascii.SP =>
       IF pauseWanted
       THEN { WriteString[" continuing"L]; pauseWanted ← FALSE }
       ELSE pauseWanted ← TRUE;
     '? =>
       BEGIN
       WriteString["Commands are: Fast, Host, Quit, Replay, Slow"];
       IF version # boot THEN WriteString[", Write-log"L];
       IF NOT pauseWanted
       THEN { WriteChar[Ascii.CR]; WriteString["Type <SP> to pause"L] };
       END;
     ENDCASE => WriteChar['?];
     IF NOT pauseWanted OR alreadyPaused THEN WriteChar[Ascii.CR];
     IF pauseWanted THEN WriteString["Type <SP> to continue ..."L];
     DeactivateInput[];
     c ← key.get[key];
     ActivateInput[];
   ENDLOOP;
   END;



-- Initialisation --

version: { bcd, image, boot } = SELECT TRUE FROM
    FrameDefs.IsBound[MMOps.MakeBoot] => boot,
    FrameDefs.IsBound[DisplayDefs.DestroyDisplay] => bcd,
  ENDCASE => image;

lookerProcess: PROCESS;
key: StreamDefs.KeyboardHandle;

-- Our display module hasn't been started yet --

SELECT version FROM
  image => { StreamDefs.StartKeyHandler[]; STOP };
  bcd =>   { DisplayDefs.DestroyDisplay[] };
  boot =>  { IF KeyDefs.Keys.Spare1=up THEN MMOps.MakeBoot[] };
ENDCASE => ERROR;

-- We're now fully running, possibly after ether-booting --

myClockFormat ←
  SELECT OsStaticDefs.OsStatics.AltoVersion.engineeringnumber FROM
    2,3 => AltoII,
  ENDCASE => AltoI;

LookerDefs.WriteTitle[herald];

key ← StreamDefs.GetDefaultKey[];

InitTabs[];

lookerProcess ← FORK LookerMain[];

TakeEthernet[];

HandleInput[];

ProcessDefs.Abort[lookerProcess]; JOIN lookerProcess;

SystemDefs.FreeHeapNode[etherBuffers];
SystemDefs.FreeHeapNode[buffers];
SystemDefs.FreeHeapNode[times];

END.