-- Pupwatch: communications and user interface

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

-- Andrew Birrell February 17, 1983 1:14 pm --

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

Ascii  USING[ CR, SP ],
BufferDefs USING[ Buffer, PupBuffer ],
--CommonPineDefs USING[DataRequest, Parameters],
ConvertUnsafe USING[ ToRope ],
DriverDefs USING[ ChangeNumberOfInputBuffers, GetDeviceChain ],
Heap  USING[ systemZone ],
Inline  USING[ BITAND, LongCOPY, HighHalf, LowHalf ],
LookerDefs USING[ Clear, DiskChar, DiskCommit, DiskMultiple, DiskPos,
   GetLength, InputAction, NotePausing, NoteSlow, ScreenLines,
   SendNow, SetPos, DisplayChar, DisplayMultiple, WriteTitle ],
--PrivateCommonPineDefs USING[pLeader],
Process  USING[ Abort, EnableAborts, Priority, priorityBackground, SetPriority ],
PupDefs  USING[ GetLocalPupAddress, GetPupAddress, PupAddress, PupNameTrouble, PupPackageMake ],
PupTypes,
Rope  USING[ Fetch, Length, ROPE ],
RPCWatch USING[ SetSpyProc ],
Runtime  USING[ IsBound ],
Space   USING[ Create, CreateUniformSwapUnits, Delete, Handle, LongPointer, Map, virtualMemory, wordsPerPage ],
SpecialCommunication USING[ SetEthernetOneListener, SetSpyProc ],
String,
System USING[ Pulses, PulsesToMicroseconds ],
TeledebugProtocol;

Looker: MONITOR

IMPORTS ConvertUnsafe, DriverDefs, Heap, Inline, LookerDefs, Process, PupDefs, Rope, RPCWatch, Runtime, Space, SpecialCommunication, String, System
EXPORTS LookerDefs
SHARES BufferDefs =

BEGIN

MSec: PROC[rawClock: System.Pulses] RETURNS[ LONG CARDINAL ] = INLINE
{ RETURN[System.PulsesToMicroseconds[rawClock]/1000] };


-- Pup level --

bufferSize: CARDINAL;
maxPupLength: CARDINAL;
bigPupLength: CARDINAL = PupTypes.maxDataBytesPerGatewayPup;
smallPupLength: 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
big => [bigChars: PACKED ARRAY[0..bigPupLength) OF CHARACTER],
small => [smallChars: PACKED ARRAY[0..smallPupLength) OF CHARACTER],
pupChars => [pupChars: PACKED ARRAY[0..0) OF CHARACTER],
pupBytes => [pupBytes: PACKED ARRAY[0..0) OF [0..377B]],
pupWords => [pupWords: ARRAY[0..0) 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: PupTypes.PupErrorCode,
errorOptions: WORD,
errorText: PACKED ARRAY [0..0) OF CHARACTER],
nameIs => [nameIs: ARRAY [0..0) OF PupDefs.PupAddress ],
ENDCASE ];

Buffer: TYPE = LONG POINTER TO BufferData;

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

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

bufferSpace: Space.Handle;
buffers: LONG POINTER TO --ARRAY BufferIndex OF-- BufferData ← NIL;

times: LONG POINTER TO ARRAY BufferIndex OF System.Pulses ← NIL;

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;
lookerWaiting: BOOLEAN ← FALSE;
allUsed: BOOLEAN ← FALSE; -- whether all buffers have been written --

AllocBuffers: ENTRY PROC[big: BOOLEAN] =
BEGIN
IF buffers # NIL THEN FreeBuffers[];
bufferSize ← IF big THEN SIZE[big BufferData] ELSE SIZE[small BufferData];
bufferSpace ← Space.Create[
size: (LONG[nBuffers] * bufferSize + Space.wordsPerPage-1) / Space.wordsPerPage,
parent: Space.virtualMemory];
Space.Map[bufferSpace];
IF big THEN Space.CreateUniformSwapUnits[size: 20, parent: bufferSpace];
buffers ← Space.LongPointer[bufferSpace];
maxPupLength ← IF big THEN bigPupLength ELSE smallPupLength;
times ← Heap.systemZone.NEW[ARRAY BufferIndex OF System.Pulses];
InnerFlush[];
END;

FreeBuffers: PROC =
BEGIN
Space.Delete[bufferSpace];
Heap.systemZone.FREE[@times];
END;

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;
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 =
{ InnerFlush[] };

InnerFlush: INTERNAL PROC =
BEGIN
rBuffer ← wBuffer ← 0;
fullBuffers ← 0; allUsed ← FALSE; lost ← ALL[FALSE]; logged ← ALL[FALSE];
waitingForBuffers ← FALSE;
NOTIFY bufferChange;
END;

waitingForBuffers: BOOLEAN ← FALSE;

NextBuffer: INTERNAL PROC = --INLINE--
BEGIN
IF fullBuffers >= nBuffers/2
OR ( waitingForBuffers AND fullBuffers+50 >= nBuffers/2 --make sure we have 50 free--)
THEN { waitingForBuffers ← TRUE; lost[wBuffer] ← TRUE; RETURN };
waitingForBuffers ← FALSE;
IF wBuffer = LAST[BufferIndex]
THEN { allUsed ← TRUE; wBuffer ← 0 }
ELSE wBuffer ← SUCC[wBuffer];
fullBuffers ← fullBuffers+1;
IF lookerWaiting THEN NOTIFY bufferChange;
logged[wBuffer] ← lost[wBuffer] ← FALSE;
END;

wanted: PupDefs.PupAddress;
myNet: CARDINAL ← 0;

broadcast: BOOLEAN ← FALSE;

ShowBroadcast: PUBLIC --ENTRY-- PROC[wanted: BOOLEAN] =
BEGIN
broadcast ← wanted;
END;

Driver: ENTRY PROC[b: BufferDefs.Buffer] RETURNS[BOOLEAN] =
BEGIN
IF b.encapsulation.ethernetType = pup
AND ( ( (b.dest.host = wanted.host OR (broadcast AND b.dest.host = 0) OR wanted.host=0)
AND ( b.dest.net = wanted.net
OR ( b.dest.net = 0 AND wanted.net = myNet )
OR wanted.net = 0
)
)
OR ( ( b.source.host = wanted.host OR wanted.host = 0 )
AND ( b.source.net = wanted.net
OR ( b.source.net = 0 AND wanted.net = myNet )
OR wanted.net = 0
)
)
)
THEN BEGIN
myBuffer: BufferIndex = wBuffer;
times[myBuffer] ← b.time;
Inline.LongCOPY[from: @b.bufferBody, to: buffers + myBuffer * bufferSize,
nwords: MIN[ (b.pupLength+1)/2, bufferSize ] ];
NextBuffer[];
END;
RETURN[TRUE]--pkt still belongs to dispatcher--
END;

RPCDriver: PROC[b: BufferDefs.PupBuffer] =
BEGIN
[] ← Driver[LOOPHOLE[b]];
--TEMP: patch to prevent RPCRuntime getting confused --
IF b.dest.host # 0 AND b.dest.host # realHost THEN b.pupType ← LOOPHOLE[0];
END;

realHost: CARDINAL;

TakeEthernet: ENTRY PROC =
BEGIN
realHost ← DriverDefs.GetDeviceChain[].hostNumber;
DriverDefs.ChangeNumberOfInputBuffers[TRUE];
IF NOT SpecialCommunication.SetEthernetOneListener[
physicalOrder: 1, newHostNumber: 0--promiscuous--]
THEN ERROR;
SpecialCommunication.SetSpyProc[Driver];
IF Runtime.IsBound[RPCWatch.SetSpyProc] THEN RPCWatch.SetSpyProc[RPCDriver];
END;

GiveEthernet: ENTRY PROC =
BEGIN
DriverDefs.ChangeNumberOfInputBuffers[FALSE];
IF NOT SpecialCommunication.SetEthernetOneListener[
physicalOrder: 1, newHostNumber: realHost]
THEN ERROR;
SpecialCommunication.SetSpyProc[NIL];
IF Runtime.IsBound[RPCWatch.SetSpyProc] THEN RPCWatch.SetSpyProc[NIL];
END;





-- Synchronization with user type-in --

inputActive: BOOLEAN ← FALSE;
lookerActive: BOOLEAN ← FALSE;
pauseWanted: BOOLEAN ← FALSE;
inputChange: CONDITION;
lookerChange: CONDITION;
wantedPriority: Process.Priority ← Process.priorityBackground;
lookerPriority: Process.Priority;

ActivateLooker: ENTRY PROC RETURNS[ BOOLEAN ] = --INLINE--
BEGIN
ENABLE UNWIND => NULL;
IF lookerPriority # wantedPriority
THEN Process.SetPriority[lookerPriority ← wantedPriority];
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;
WHILE inputActive DO WAIT inputChange ENDLOOP;
inputActive ← TRUE;
WHILE lookerActive DO WAIT lookerChange ENDLOOP;
END;

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

NewPriority: PUBLIC ENTRY PROC[new: Process.Priority] =
BEGIN
wantedPriority ← new;
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: REAL;
tabTo: REAL;
tabAddr: REAL;
tabPkt: REAL;
tabData: REAL;

-- 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;
diskDetails: CARDINAL;

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

WriteChar: PROC[CHARACTER] ← LookerDefs.DisplayChar;

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

WriteString: PROC[s: LONG 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 Inline.HighHalf[n] # 0 THEN WriteOctal[Inline.HighHalf[n]];
WriteOctal[Inline.LowHalf[n]];
END;

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

WriteDecimal: PROC[n: CARDINAL] = INLINE
{ s: STRING = [5]; String.AppendDecimal[s,n]; WriteString[s] };

WriteAddr: PROC[addr: PupDefs.PupAddress] = INLINE
BEGIN
WriteOctal777[addr.net];
WriteChar['#];
WriteOctal777[addr.host];
WriteChar['#];
WriteOctal777[Inline.BITAND[addr.socket.b,777B]];
END;

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

prevMS: LONG CARDINAL ← 0;

-- 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];
lookupFileSocket: PupTypes.PupSocketID = [a:0,b:61B];
teleSwatSocket: PupTypes.PupSocketID = TeledebugProtocol.teleSwatSocket;

Watch: PROC[ b: Buffer, time: System.Pulses ] =
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;
lookupFile: BOOLEAN = b.source.socket = lookupFileSocket
OR b.dest.socket = lookupFileSocket;
teleSwat: BOOLEAN = b.source.socket = teleSwatSocket
OR b.dest.socket = teleSwatSocket;
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[Inline.LowHalf[(newMS-prevMS)/1000]];
WriteChar['s];
END
ELSE WriteDecimal[Inline.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,spare,rfa}
LOOPHOLE[140B] => "RPC-call-end",
LOOPHOLE[141B] => "RPC-data-end",
LOOPHOLE[142B] => "RPC-ack",
LOOPHOLE[143B] => "RPC-spare",
LOOPHOLE[144B] => "RPC-RFA",
LOOPHOLE[150B] => "RPC-a-call-end",
LOOPHOLE[151B] => "RPC-a-data-end",
LOOPHOLE[152B] => "RPC-ping",
LOOPHOLE[153B] => "RPC-a-spare",
LOOPHOLE[154B] => "RPC-a-RFA",
LOOPHOLE[160B] => "RPC-call-more",
LOOPHOLE[161B] => "RPC-data-more",
LOOPHOLE[170B] => "RPC-a-call-more",
LOOPHOLE[171B] => "RPC-a-data-more",

-- unregistered pup types (possible overlap)
gatewayRequest--200-- =>
IF lookupFile THEN "fileLookup" ELSE "gatewayRequest",
--also dateTextRequest (socket 4), statisticsRequest (socket 22)
gatewayInfo--201-- =>
IF lookupFile THEN "fileInfo" ELSE "gatewayInfo",
--also dateTextIs (socket 4), statisticsAre (socket 22)
dateTenexRequest--202-- =>
IF lookupFile THEN "fileError"
ELSE IF teleSwat THEN "go" ELSE "dateTenexRequest",
dateTenexIs--203-- =>
IF teleSwat THEN "goReply" ELSE "dateTenexIs",
LOOPHOLE[204B] =>
IF teleSwat THEN "ack" ELSE "pt204",
dateAltoRequest--206-- => "dateAltoRequest",
dateAltoIs--207-- => "dateAltoIs",
mailCheck--210-- => "mailCheck",
mailIsNew--211-- => "mailIsNew",
mailNotNew--212-- => "mailNotNew",
mailError--213-- => "mailError",
mailCheckLaurel--214-- => "mailCheckL",
nameLookup--220-- => "nameLookup",
nameIs--221-- => "nameIs",
nameError--222-- => "nameError",
addressLookup--223-- => "addrLookup",
addressIs--224-- => "addrIs",
whereIsUser--230-- => "whereIsUser",
userIs--231-- => "userIs",
userError--232-- => "userError",
netDirVersion--240-- => "netDirVersion",
--also eventReport (socket 30)
sendNetDir--241-- => "sendNetDir",
--also eventReportReply (socket 30)
bootFileSend--244-- => "bootFileSend",
kissOfDeath--247-- => "kissOfDeath",
request--250-- =>
IF mscSrv THEN "userAuthReq" ELSE "request",
result--251-- =>
IF mscSrv THEN "userAuthOk" ELSE "result",
unsolicited--252-- =>
IF mscSrv THEN "userAuthBad" ELSE "unsolicited",
custodian--253-- => "custodian",
sync--254-- => "sync",
pineAck--255-- => "pineAck",
noop--256-- => "noop",
bootDirReq--257-- => "bootDirReq",
bootDirReply--260-- =>
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?",
TeledebugProtocol.coreStoreRequest--300-- => "wCore",
TeledebugProtocol.coreFetchRequest--301-- => "rCore",
TeledebugProtocol.diskAddressSetRequest--302-- => "diskAddr",
TeledebugProtocol.diskStoreRequest--303-- => "wDisk",
TeledebugProtocol.diskFetchRequest--304-- => "rDisk",
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
badChecksumPupErrorCode =>
WriteString["badChecksum"];
noProcessPupErrorCode =>
WriteString["noSuchPort"];
resourceLimitsPupErrorCode =>
WriteString["resourceLimits"];
inconsistentPupErrorCode =>
WriteString["inconsistentPup"];
cantGetTherePupErrorCode =>
WriteString["cantGetThere"];
hostDownPupErrorCode =>
WriteString["hostDown"];
eightHopsPupErrorCode =>
WriteString["sixteenHops"];
tooBigPupErrorCode =>
WriteString["tooBigPup"];
iAmNotAGatewayPupErrorCode =>
WriteString["iAmNotAGateway"];
gatewayResourceLimitsPupErrorCode =>
WriteString["gatewayResources"];
ENDCASE => WriteOctal[LOOPHOLE[b.errorCode]];
mailCheck, mailCheckLaurel, nameLookup =>
IF mode # disk THEN 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;
IN [ LOOPHOLE[140B]..LOOPHOLE[171B] ] => --Cedar RPC packet--
BEGIN
--*** RPC header layout, stolen from RPCPkt.mesa --
RPCHeader: TYPE = MACHINE DEPENDENT RECORD[
length (0:0..14): [0..77777B],
oddByte (0:15..15): { no(0), yes(1) },
type (1):  RPCPktType,
destPSB (2):  CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits
srcePSB (3):  CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits
destHost (4):  CARDINAL--Machine--,
destSoc (5):  PupTypes.PupSocketID,
srceHost (7):  CARDINAL--Machine--,
srceSoc (8):  PupTypes.PupSocketID,
-- end of standard Pup header --
convLS (10):  CARDINAL--PktConversationID--,
convMS (11):  CARDINAL,
-- For secure conversations, the remainder of the packet must be encrypted --
pktID (12):  RPCPktID,
dispatcher (16): DispatcherDetails ];
RPCPktType: TYPE = MACHINE DEPENDENT RECORD[ -- "type" word of a Pup --
transport (0:0..7): [0..255], -- should be zero before sending --
subType (0:8..10): { rpc(3B), (7B) },
eom (0:11..11): { end(0), notEnd(1) },
ack (0:12..12): { dontAck(0), pleaseAck(1) },
class (0:13..15): { call(0), data(1), ack(2), rfa(4), (7) } ];
RPCPktID: TYPE = MACHINE DEPENDENT RECORD[
-- [ConversationID,PktID] uniquely identifies pkt for all hosts and time --
activity(0): CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits--
callSeqLS(1): CARDINAL--CallCount--,
callSeqMS(2): CARDINAL,
pktSeq(3): CARDINAL ];
DispatcherDetails: TYPE = MACHINE DEPENDENT RECORD[
mds: CARDINAL, -- top half of dispatcher's MDS base address --
dispatcherID: LONG CARDINAL--DispatcherID--,
dispatcherHint: CARDINAL--ExportHandle-- -- hint to exporter host's export table -- ];
--*** --
h: LONG POINTER TO RPCHeader = LOOPHOLE[b];
overhead: CARDINAL = SIZE[RPCHeader]+1--checksum--;
WriteString["Len:"];
IF h.length < overhead
THEN { WriteString["?-"L]; WriteOctal777[overhead-h.length] }
ELSE WriteOctal777[h.length-overhead];
WriteString[",Cnv:"]; WriteOctal777[h.convLS MOD 1000B];
WriteString[",Call:"]; WriteOctal777[h.pktID.callSeqLS MOD 1000B];
WriteString[",Pkt:"]; WriteOctal777[h.pktID.pktSeq MOD 1000B];
IF h.type.class = call
THEN { WriteString[",Disp:"]; WriteOctal777[h.dispatcher.dispatcherHint MOD 1000B] };
END;
addressLookup =>
WriteAddr[b.nameIs[0]];
gatewayRequest =>
IF lookupFile AND mode # disk THEN WritePup[b];
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;
TeledebugProtocol.coreStoreRequest, TeledebugProtocol.coreFetchRequest =>
IF teleSwat
THEN WriteLongOctal[LOOPHOLE[@b.pupBody,
LONG POINTER TO TeledebugProtocol.CoreStoreRequest].page];
TeledebugProtocol.diskAddressSetRequest, TeledebugProtocol.diskFetchRequest =>
IF teleSwat AND ContentsBytes[b] = 2*SIZE[TeledebugProtocol.DiskAddressSetRequest]
THEN WriteLongOctal[LOOPHOLE[@b.pupBody,
LONG POINTER TO TeledebugProtocol.DiskAddressSetRequest].page];
ENDCASE => DefaultBodyPrintout[b];
WriteChar[']];
IF mode = disk
THEN BEGIN
LookerDefs.DiskPos[diskDetails];
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[diskFrom];
LookerDefs.DiskPos[diskPkt];
IF l = 0 THEN WriteString["{empty}"] ELSE WritePup[b];
WriteChar[Ascii.CR];
LookerDefs.DiskPos[diskFrom];
LookerDefs.DiskPos[diskPkt];
WriteString["Bytes="];
IF l = 0
THEN WriteString["{empty}"]
ELSE FOR i: CARDINAL IN [0..l)
DO WriteOctal777[b.pupBytes[i]];
WriteChar[Ascii.SP]
ENDLOOP;
WriteChar[Ascii.CR];
LookerDefs.DiskPos[diskFrom];
LookerDefs.DiskPos[diskPkt];
WriteString["Words="];
IF l = 0
THEN WriteString["{empty}"]
ELSE FOR i: CARDINAL IN [0..l/2)
DO WriteOctal[b.pupWords[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;

StartPause: PROC =
BEGIN
WriteString["Pausing ..."L]; LookerDefs.SendNow[];
LookerDefs.NotePausing[TRUE]; pauseWanted ← TRUE;
END;

LookerMain: PROC =
BEGIN
countLimit: CARDINAL = 40;
Process.SetPriority[lookerPriority ← wantedPriority];
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 * bufferSize, times[this]];
ReturnBuffer[];
IF speed = slow
AND (lookerCount ← lookerCount+1) >= (LookerDefs.ScreenLines[] * 3) / 4
THEN StartPause[];
DeactivateLooker[];
END;
ENDLOOP;
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 * bufferSize, times[this]];
logged[this] ← TRUE;
ReturnBuffer[];
ENDLOOP;
WriteChar ← LookerDefs.DisplayChar;
WriteMultiple ← LookerDefs.DisplayMultiple;
mode ← display;
LookerDefs.DiskCommit[];
END;



-- user command input --

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

DoAction: PUBLIC PROC[act: LookerDefs.InputAction] =
-- InputAction: TYPE = RECORD[SELECT act: * FROM
-- fast => NULL,
-- newHost => [name: Rope.ROPE],
-- pauseContinue => NULL,
-- quit => NULL,
-- replay => NULL,
-- slow => NULL,
-- writeLog => NULL,
-- pktSize => [big: BOOL],
-- ENDCASE];
BEGIN
alreadyPaused: BOOLEAN;
ActivateInput[];
alreadyPaused ← pauseWanted;
IF pauseWanted AND act.act # pauseContinue THEN WriteChar[Ascii.CR];
lookerCount ← 0;
WITH act: act SELECT FROM
fast =>
BEGIN
WriteString["Fast"L];
speed ← fast; LookerDefs.NoteSlow[FALSE]; pauseWanted ← FALSE;
END;
newHost =>
BEGIN
WriteString["Host ... "]; LookerDefs.SendNow[];
GiveEthernet[]; -- stops driver --
BEGIN
outcome: LookupOutcome;
net, host: [0..255];
[outcome, net, host] ← Lookup[act.name];
SELECT outcome FROM
ok =>
BEGIN
WriteString["ok"L]; LookerDefs.SendNow[];
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];
noRoute => WriteString["no route to that host"L];
ENDCASE => ERROR;
END;
TakeEthernet[]; -- starts driver --
END;
pktSize =>
AllocBuffers[big: act.big];
replay =>
BEGIN
LookerDefs.Clear[];
WriteString["Replay (Slow)"L];
ReplayBuffers[]; speed ← slow; LookerDefs.NoteSlow[TRUE];
pauseWanted ← FALSE;
END;
slow =>
BEGIN
WriteString["Slow"L];
speed ← slow; LookerDefs.NoteSlow[TRUE];
pauseWanted ← FALSE;
END;
start =>
BEGIN
InitTabs[];
AllocBuffers[big: FALSE];
PupDefs.PupPackageMake[];
wanted ← PupDefs.GetLocalPupAddress[,NIL]; myNet ← wanted.net;
WriteTitle[];
pauseWanted ← FALSE;
lookerProcess ← FORK LookerMain[];
TakeEthernet[];
END;
stop =>
BEGIN
Process.Abort[lookerProcess];
JOIN lookerProcess;
GiveEthernet[];
FreeBuffers[];
END;
writeLog =>
IF version = boot
THEN WriteString["""Write log"" isn't implemented in this version"L]
ELSE BEGIN
WriteString["Writing log file ... "L]; LookerDefs.SendNow[];
WriteDiskLog[];
WriteString["ok"L];
END;
pauseContinue =>
IF pauseWanted
THEN { WriteString[" continuing"L]; pauseWanted ← FALSE; }
ELSE StartPause[];
ENDCASE => ERROR;
IF NOT pauseWanted OR alreadyPaused THEN WriteChar[Ascii.CR];
IF NOT pauseWanted AND act.act # stop THEN LookerDefs.NotePausing[FALSE];
DeactivateInput[];
END;

-- Address Lookup --

LookupOutcome: TYPE = { ok, badName, noResponse, noRoute };

Lookup: PROC[name: Rope.ROPE]
  RETURNS[outcome: LookupOutcome, net, host: [0..255]] =
BEGIN
addr: PupDefs.PupAddress;
s: STRING = [99];
length: INT = Rope.Length[name];
FOR i: INT IN [0..IF s.maxlength > length THEN length ELSE s.maxlength)
DO s[i] ← Rope.Fetch[name, i]; s.length ← i+1; ENDLOOP;
outcome ← ok;
PupDefs.GetPupAddress[@addr, IF s.length = 0 THEN "ME"L ELSE s !
PupDefs.PupNameTrouble =>
{ outcome ← SELECT code FROM noRoute => noRoute, noResponse => noResponse,
ENDCASE => badName;
CONTINUE } ];
net ← addr.net; host ← addr.host;
END;


-- Initialisation --

version: { bcd, image, boot } ← bcd;

lookerProcess: PROCESS;

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

SELECT version FROM
image => { STOP };
bcd => { NULL };
boot => { NULL };
ENDCASE => ERROR;

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

Process.EnableAborts[@bufferChange];
Process.EnableAborts[@inputChange];
Process.EnableAborts[@lookerChange];

END.