-- File: EchoTool.mesa -- BLyon, January 16, 1981 5:58 PM -- HGM, March 14, 1981 12:25 PM -- Please don't forget to update the herald... DIRECTORY AddressTranslation USING [ AppendMyHostNumber, AppendNetworkAddress, StringToNetworkAddress], BufferDefs USING [BufferAccessHandle, OisBuffer], Echo USING [echoRequest, echoResponse], FormSW USING [ AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem, ItemHandle, line0, line3, line4, line5, line6, newLine, NotifyProcType, NumberItem, ProcType, StringItem], Inline USING [BITAND, BITNOT], MsgSW USING [Post], OISCP USING [ OiscpPackageDestroy, OiscpPackageMake, GetFreeSendOisBufferFromPool, ReturnFreeOisBuffer], OISCPTypes USING [bytesPerPktHeader, bytesPerPktText], OISCPConstants USING [unknownSocketID, echoerSocket], Process USING [Yield, Detach], Put USING [Char, CR, Line, Text], Router USING [FindMyHostID, SetOisCheckit, SetOisDriverLoopback], SpecialSystem USING [HostNumber, NetworkAddress], Socket USING [ Abort, AssignNetworkAddress, Create, Delete, GetPacket, PutPacket, SetWaitTime, TimeOut, TransferStatus], SocketInternal USING [GetBufferPool, SocketHandle], StatsDefs USING [StatPrintCurrent, StatReady, StatSince], Storage USING [Free, FreeStringNil, String], String USING [AppendNumber, AppendLongNumber, AppendString], System USING [], Time USING [AppendCurrent], Tool USING [ Create, UnusedLogName, MakeFormSW, MakeFileSW, MakeMsgSW, MakeSWsProc], ToolWindow USING [TransitionProcType], Window USING [Handle, Place]; EchoTool: PROGRAM IMPORTS FormSW, Inline, MsgSW, OISCP, Process, Put, AddressTranslation, Router, Socket, SocketInternal, StatsDefs, Storage, String, Time, Tool EXPORTS Socket, System SHARES BufferDefs = BEGIN -- EXPORTED TYPE(S) NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress; ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle; -- global variable declarations msg, log, form: Window.Handle ← NIL; thisMachineID, localAddress, remoteAddress: STRING ← NIL; localAddr: SpecialSystem.NetworkAddress; maxLength, maxEchos: CARDINAL ← 256; running, pleaseStop: BOOLEAN ← FALSE; driverLoopBack: BOOLEAN ← TRUE; useMax: BOOLEAN ← FALSE; fixedLength: BOOLEAN ← FALSE; verbose: BOOLEAN ← TRUE; checkIt: BOOLEAN ← TRUE; checksums: BOOLEAN ← FALSE; doStats: BOOLEAN ← FALSE; Init: PROCEDURE = BEGIN [] ← Tool.Create[ name: "Echo Tool of March 14, 1981"L, makeSWsProc: MakeThisTool, clientTransition: Transition]; END; MakeThisTool: Tool.MakeSWsProc = BEGIN logFileName: STRING = [40]; msg ← Tool.MakeMsgSW[window: window, lines: 1]; form ← Tool.MakeFormSW[window: window, formProc: MakeItemArray]; Tool.UnusedLogName[logFileName, "EchoTool.log$"L]; log ← Tool.MakeFileSW[window: window, name: logFileName]; END; MakeItemArray: FormSW.ClientItemsProcType = BEGIN echoStartPlace: Window.Place = [x: 2, y: FormSW.line0]; stopPlace: Window.Place = [x: 10*7, y: FormSW.line0]; useMaxPlace: Window.Place = [x: 20*7, y: FormSW.line0]; maxEchosPlace: Window.Place = [x: 30*7, y: FormSW.line0]; thisMachineIDPlace: Window.Place = [x: 2, y: FormSW.line3]; localAddressPlace: Window.Place = [x: 2, y: FormSW.line4]; remoteAddressPlace: Window.Place = [x: 2, y: FormSW.line5]; checkItPlace: Window.Place = [x: 2, y: FormSW.line6]; verbosePlace: Window.Place = [x: 15*7, y: FormSW.line6]; checksumsPlace: Window.Place = [x: 30*7, y: FormSW.line6]; driverLoopBackPlace: Window.Place = [x: 45*7, y: FormSW.line6]; nItems: CARDINAL = 17; i: INTEGER ← -1; items ← FormSW.AllocateItemDescriptor[nItems]; items[i ← i + 1] ← FormSW.CommandItem[ tag: "Echo"L, place: echoStartPlace, proc: EchoProc]; items[i ← i + 1] ← FormSW.CommandItem[ tag: "Stop"L, place: stopPlace, proc: Stop]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "UseMax"L, place: useMaxPlace, switch: @useMax]; items[i ← i + 1] ← FormSW.NumberItem[ tag: "MaxEchos"L, place: maxEchosPlace, value: @maxEchos]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "FixedLength"L, place: FormSW.newLine, switch: @fixedLength]; items[i ← i + 1] ← FormSW.NumberItem[tag: "(Max)Length"L, value: @maxLength]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "DoStats"L, switch: @doStats, place: FormSW.newLine]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Ready"L, proc: Ready]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Recent"L, proc: Since]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Totals"L, proc: Totals]; items[i ← i + 1] ← FormSW.StringItem[ tag: "ThisMachineID"L, place: thisMachineIDPlace, string: @thisMachineID, readOnly: TRUE]; items[i ← i + 1] ← FormSW.StringItem[ tag: "LocalSocket"L, place: localAddressPlace, string: @localAddress, inHeap: TRUE, readOnly: TRUE]; items[i ← i + 1] ← FormSW.StringItem[ tag: "RemoteAddress"L, place: remoteAddressPlace, string: @remoteAddress, inHeap: TRUE]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "CheckIt"L, place: checkItPlace, switch: @checkIt]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "Verbose"L, place: verbosePlace, switch: @verbose]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "Checksums"L, place: checksumsPlace, proc: ToggleChecksums, switch: @checksums]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "DriverLoopBack"L, place: driverLoopBackPlace, proc: ToggleDriverLoopBack, switch: @driverLoopBack]; IF (i + 1) # nItems THEN ERROR; RETURN[items, TRUE]; END; Transition: ToolWindow.TransitionProcType = BEGIN IF old = inactive THEN -- tool is becomming active BEGIN myHostID: SpecialSystem.HostNumber; OISCP.OiscpPackageMake[]; myHostID ← Router.FindMyHostID[]; Router.SetOisDriverLoopback[driverLoopBack ← TRUE]; Router.SetOisCheckit[checksums ← TRUE]; useMax ← FALSE; verbose ← checkIt ← TRUE; thisMachineID ← Storage.String[1 + 6*SIZE[SpecialSystem.HostNumber]]; AddressTranslation.AppendMyHostNumber[thisMachineID]; localAddr ← Socket.AssignNetworkAddress[]; localAddress ← Storage.String[2 + 6*SIZE[SpecialSystem.NetworkAddress]]; AddressTranslation.AppendNetworkAddress[localAddress, localAddr]; END ELSE IF new = inactive THEN BEGIN thisMachineID ← Storage.FreeStringNil[thisMachineID]; localAddress ← Storage.FreeStringNil[localAddress]; remoteAddress ← Storage.FreeStringNil[remoteAddress]; OISCP.OiscpPackageDestroy[]; END; END; ToggleDriverLoopBack: FormSW.NotifyProcType = BEGIN Router.SetOisDriverLoopback[driverLoopBack]; Put.Text[log, "DriverLoopBack is now "L]; Put.Line[log, IF driverLoopBack THEN "ON."L ELSE "OFF."L]; END; ToggleChecksums: FormSW.NotifyProcType = BEGIN Router.SetOisCheckit[checksums]; Put.Text[log, "Checksums are now "L]; Put.Line[log, IF checksums THEN "ON."L ELSE "OFF."L]; END; Stop: FormSW.ProcType = BEGIN pleaseStop ← TRUE; WHILE running DO Process.Yield[]; ENDLOOP; END; Ready: FormSW.ProcType = BEGIN StatsDefs.StatReady[]; END; Since: FormSW.ProcType = BEGIN StatsDefs.StatSince[log]; END; Totals: FormSW.ProcType = BEGIN StatsDefs.StatPrintCurrent[log]; END; EchoProc: FormSW.ProcType = BEGIN p: PROCESS; useLimit, fixed: BOOLEAN; limit, size: CARDINAL; errFlag: BOOLEAN ← FALSE; myHostID: SpecialSystem.HostNumber ← Router.FindMyHostID[]; remoteAddr: SpecialSystem.NetworkAddress; cH: ChannelHandle; MsgSW.Post[msg, ""L]; IF running THEN BEGIN MsgSW.Post[msg, "An echoer is already running; use Stop to quit it."L]; errFlag ← TRUE; END; useLimit ← useMax; limit ← maxEchos; fixed ← fixedLength; size ← maxLength; remoteAddr ← AddressTranslation.StringToNetworkAddress[remoteAddress ! ANY => BEGIN MsgSW.Post[msg, "RemoteAddress is incorrectly specified; try again."L]; errFlag ← TRUE; CONTINUE; END]; IF errFlag THEN RETURN; IF remoteAddr.socket=OISCPConstants.unknownSocketID THEN remoteAddr.socket ← OISCPConstants.echoerSocket; pleaseStop ← FALSE; running ← TRUE; cH ← Socket.Create[localAddr]; Socket.SetWaitTime[cH, 1500]; -- milli-seconds p ← FORK Echoer[cH, remoteAddr, useLimit, limit, fixed, size]; Process.Detach[p]; MsgSW.Post[msg, "Echoer successfully started."L]; Process.Yield[]; END; Echoer: PROCEDURE [ cH: ChannelHandle, remoteAddr: SpecialSystem.NetworkAddress, useMaxEcho: BOOLEAN, maxEcho: CARDINAL, fixed: BOOLEAN, size: CARDINAL] = BEGIN length: CARDINAL = MIN[OISCPTypes.bytesPerPktText/2, 400B, size]; picks: ARRAY [0..16] OF CARDINAL; drops: ARRAY [0..16] OF CARDINAL; sent, good, missed, late, bad, horrible, error: LONG CARDINAL; ClearCounters: PROCEDURE = BEGIN sent ← good ← missed ← late ← bad ← horrible ← error ← 0; picks ← ALL[0]; drops ← ALL[0]; END; AddToHist: PROCEDURE [ hist: POINTER TO ARRAY [0..16] OF CARDINAL, bits: WORD] = BEGIN i: CARDINAL; IF bits = 0 THEN RETURN; SELECT bits FROM 1 => i ← 15; 2 => i ← 14; 4 => i ← 13; 10B => i ← 12; 20B => i ← 11; 40B => i ← 10; 100B => i ← 9; 200B => i ← 8; 400B => i ← 7; 1000B => i ← 6; 2000B => i ← 5; 4000B => i ← 4; 10000B => i ← 3; 20000B => i ← 2; 40000B => i ← 1; 100000B => i ← 0; ENDCASE => i ← 16; hist[i] ← hist[i] + 1; END; PrintSummary: PROCEDURE = BEGIN Put.CR[log]; WriteLongDecimal[sent]; Put.Line[log, " packets sent."L]; ShowPercent[good, "good packets received."L]; ShowPercent[missed, "packets missed."L]; ShowPercent[late, "late (or??) packets received."L]; ShowPercent[bad, "bad packets received."L]; ShowPercent[horrible, "packets received with more than 10 words wrong."L]; ShowPercent[error, "error packets received."L]; IF bad # 0 THEN BEGIN x: WORD ← 100000B; Put.CR[log]; Put.Line[log, " Bit Picked Dropped"L]; FOR i: CARDINAL IN [0..16] DO IF picks[i] # 0 OR drops[i] # 0 THEN BEGIN IF i = 16 THEN Put.Text[log, " Other"L] ELSE O6[x]; D8[picks[i]]; D8[drops[i]]; Put.CR[log]; END; x ← x/2; ENDLOOP; END; END; ShowPercent: PROCEDURE [n: LONG CARDINAL, s: STRING] = BEGIN IF n = 0 THEN RETURN; WriteLongDecimal[n]; Put.Text[log, " ("L]; WriteLongDecimal[n*100/sent]; Put.Text[log, "%) "L]; Put.Line[log, s]; END; WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE BEGIN temp: STRING = [25]; String.AppendNumber[temp, n, radix]; THROUGH [temp.length..width) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, temp]; END; WriteLongDecimal: PROCEDURE [n: LONG UNSPECIFIED] = BEGIN temp: STRING = [32]; temp.length ← 0; String.AppendLongNumber[temp, n, 10]; Put.Text[log, temp]; END; D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END; O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END; WriteCurrentDateAndTime: PROCEDURE = BEGIN time: STRING = [32]; Time.AppendCurrent[time, ]; Put.Text[log, time]; END; PrintSocketEchoError: PROCEDURE [ b: BufferDefs.OisBuffer, status: Socket.TransferStatus] = BEGIN temp: STRING = [100]; String.AppendString[temp, "Error OIS, code="L]; String.AppendString[ temp, SELECT status FROM pending => "pending"L, aborted => "aborted"L, noRouteToNetwork => "noRouteToNetwork"L, hardwareProblem => "hardwareProblem"L, invalidDestAddr => "invalidDestinationAddr"L, goodCompletion => "goodCompletion (?!)"L, ENDCASE => "?????"L]; String.AppendString[temp, ", from: "L]; AddressTranslation.AppendNetworkAddress[temp, b.ois.source]; Put.Line[log, temp]; END; -- sequence number counter: CARDINAL ← 0; -- send/ receive stuff myBufferAccessHandle: BufferDefs.BufferAccessHandle ← SocketInternal.GetBufferPool[cH]; sendBuf, recBuf: BufferDefs.OisBuffer; pktBody: LONG POINTER TO ARRAY [0..0) OF WORD; status: Socket.TransferStatus; ClearCounters[]; IF doStats THEN StatsDefs.StatReady[]; -- note tht are two outstanding Get's before entering this loop UNTIL pleaseStop OR ((counter >= maxEcho) AND useMaxEcho) DO cycle: CARDINAL; IF (((counter ← counter + 1) MOD 5) = 0) THEN Process.Yield[]; cycle ← IF fixed THEN length ELSE counter MOD length; sendBuf ← OISCP.GetFreeSendOisBufferFromPool[myBufferAccessHandle]; sendBuf.ois.pktLength ← OISCPTypes.bytesPerPktHeader + 2*(cycle + 1); sendBuf.ois.transCntlAndPktTp.packetType ← echo; sendBuf.ois.destination ← remoteAddr; pktBody ← @sendBuf.ois.oisWords; pktBody[0] ← Echo.echoRequest; FOR k: CARDINAL IN [0..cycle) DO pktBody[k+1] ← (k*400B + counter); ENDLOOP; Socket.PutPacket[cH, sendBuf]; sent ← sent + 1; -- now receive the echo or any back logged echos DO recBuf ← Socket.GetPacket[ cH ! Socket.TimeOut => BEGIN missed ← missed + 1; Put.Char[log, '?]; EXIT; END]; status ← LOOPHOLE[recBuf.status]; pktBody ← @recBuf.ois.oisWords; SELECT TRUE FROM (status # goodCompletion) => BEGIN -- some kind of error occurred error ← error + 1; PrintSocketEchoError[recBuf, status]; OISCP.ReturnFreeOisBuffer[recBuf]; LOOP; END; (recBuf.ois.pktLength # OISCPTypes.bytesPerPktHeader + 2*(cycle + 1)) OR (pktBody[0] # Echo.echoResponse) OR ((cycle # 0) AND (pktBody[0+1] # (0*400B + counter))) => BEGIN -- probably a late packet, but could be trash, or error late ← late + 1; Put.Char[log, '#]; OISCP.ReturnFreeOisBuffer[recBuf]; LOOP; END; ENDCASE => BEGIN -- the echo we were looking for hits: CARDINAL ← 0; IF checkIt THEN FOR k: CARDINAL IN [0..cycle) DO IF pktBody[k+1] # (k*400B + counter) THEN BEGIN OPEN Inline; expected, found, picked, dropped: WORD; IF hits = 0 THEN BEGIN Put.CR[log]; WriteCurrentDateAndTime[]; Put.Text[log, " Data compare error(s) on packet number "L]; WriteLongDecimal[sent]; Put.Line[log, "."L]; Put.Line[log, "Idx Expected Found Picked Dropped"L]; END; expected ← k + cycle*400B; found ← pktBody↑[k]; picked ← BITAND[found, BITNOT[expected]]; dropped ← BITAND[expected, BITNOT[found]]; AddToHist[@picks, picked]; AddToHist[@drops, dropped]; IF hits < 10 THEN BEGIN O3[k]; O9[expected]; O9[found]; O9[picked]; O9[dropped]; Put.CR[log]; END; hits ← hits + 1; END; ENDLOOP; IF hits = 0 THEN good ← good + 1 ELSE bad ← bad + 1; IF hits = 0 AND verbose THEN Put.Char[log, '!]; IF hits > 10 THEN BEGIN horrible ← horrible + 1; Put.Line[log, "...."L]; END; OISCP.ReturnFreeOisBuffer[recBuf]; EXIT; END; ENDLOOP; ENDLOOP; -- get back the two recieve buffers Put.CR[log]; Socket.Abort[cH]; IF doStats THEN StatsDefs.StatSince[log]; Socket.Delete[cH]; PrintSummary[]; MsgSW.Post[msg, ""L]; running ← FALSE; END; Init[]; -- this gets string out of global frame END...