-- 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...