-- File: GateWatcherTool.mesa - last edit:
-- AOF                  3-Feb-88 18:46:54
-- WIrish               5-Feb-88 12:14:12
-- HGM                 25-Jun-85  3:41:20
-- Copyright (C) 1984, 1988 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Display USING [replaceFlags, Text, White],
  FormSW USING [
    AllocateItemDescriptor, ClientItemsProcType, CommandItem, Display, FindItem,
    newLine, ProcType, StringItem],
  Heap USING [systemZone],
  Inline USING [LowHalf],
  MsgSW USING [Post],
  Process USING [Detach, Pause],
  Profile USING [GetUser],
  Runtime USING [GetBcdTime, IsBound, UnboundProcedure],
  String USING [AppendString, AppendChar, AppendNumber, AppendLongNumber],
  System USING [GetClockPulses],
  Time USING [Append, AppendCurrent, Current, Unpack],
  Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, AddThisSW],
  ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, TransitionProcType],
  Window USING [Box, Handle, Place],
  WindowFont USING [FontHeight],

  BootServerDefs USING [bootStatsRequest, bootStatsReply, BootStatsEntry],
  ForwarderDefs USING [
    forwarderStatsRequest, forwarderStatsReply, ForwardStatsEntry,
    TransitMatrixEntry],
  GateControlDefs USING [
    gateControlHalt, gateControlRestart, gateControlStatsSend, gateControlStatsAck,
    GateControlStatsEntry],
  NameServerDefs USING [nameStatsRequest, nameStatsReply, NameStatsEntry],
  Password USING [Status, ValidMemberOfGroup],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy, AppendErrorPup, Body,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake,
    PupSocketKick, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes,
    AppendHostName, AppendPupAddress, GetPupAddress, PupNameTrouble,
    AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  PupEchoServerDefs USING [echoStatsRequest, echoStatsReply, EchoStatsEntry],
  PupTimeServerFormat USING [timeStatsRequest, timeStatsReply, TimeStatsEntry],
  PupTypes USING [
    PupAddress, fillInSocketID, fillInPupAddress, gatewaySoc, miscSrvSoc,
    echoSoc],
  PupWireFormat USING [BcplLongNumber, BcplToMesaLongNumber];

GateWatcherTool: MONITOR
  IMPORTS
    Display, FormSW, Heap, Inline, MsgSW, Process, Profile, Runtime, String, System,
    Time, Tool, ToolWindow, WindowFont,
    Password, PupWireFormat, PupDefs =
  BEGIN OPEN PupDefs, PupTypes;

  msg, form, info: Window.Handle ← NIL;
  z: UNCOUNTED ZONE = Heap.systemZone;

  -- Be sure to initialize it when it is allocated!!!!
  data: LONG POINTER TO Data ← NIL;
  pool: PupDefs.AccessHandle;

  maxLines: CARDINAL = 35;
  Data: TYPE = RECORD [
    pleaseStop: BOOLEAN ← FALSE,
    alighTheSillyFink: WORD ← 0,
    running: BOOLEAN ← FALSE,
    him: PupAddress ← PupTypes.fillInPupAddress,
    target: LONG STRING ← NIL,

    fastBoot, slowBoot, newBoot, echo, name, dirsSent, route, time: LONG
      CARDINAL ← 0,

    bootSoc, echoSoc, gcSoc, nameSoc, routeSoc, timeSoc: PupSocket ← NIL,

    activeProcesses: CARDINAL ← 0,
    height: CARDINAL ← 20,
    line: CARDINAL ← 0,
    lines: ARRAY [0..maxLines) OF LONG STRING ← ALL[NIL]];

  Init: PROCEDURE =
    BEGIN
    herald: LONG STRING = [100];
    String.AppendString[herald, "Gate Watcher of "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs,
      clientTransition: ClientTransition];
    END;

  GateWatcherOn: PROCEDURE =
    BEGIN
    IF data.running THEN RETURN;
    ZapCounters[];
    ClearThings[];
    data.pleaseStop ← FALSE;
    IF ~FindPath[] THEN RETURN;
    data.activeProcesses ← 6;
    pool ← PupDefs.MakePool[send: 4, receive: 4];
    Process.Detach[FORK Boot[]];
    Process.Detach[FORK Echo[]];
    Process.Detach[FORK Name[]];
    Process.Detach[FORK Route[]];
    Process.Detach[FORK Timer[]];
    Process.Detach[FORK GateControl[]];
    data.running ← TRUE;
    UpdatePicture[];
    END;

  GateWatcherOff: PROCEDURE =
    BEGIN
    IF data = NIL THEN RETURN;
    IF ~data.running THEN RETURN;
    WaitUntilStopped[];
    data.running ← FALSE;
    UpdatePicture[];
    END;

  UpdatePicture: PROCEDURE =
    BEGIN
    IF form = NIL THEN RETURN;
    FormSW.FindItem[form, startIX].flags.invisible ← data.running;
    FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running;
    IF Runtime.IsBound[LOOPHOLE[Password.ValidMemberOfGroup]] THEN
      FormSW.FindItem[form, enableIX].flags.invisible ← ~data.running;
    FormSW.Display[form];
    END;

  WaitUntilStopped: PROCEDURE =
    BEGIN
    IF data = NIL THEN RETURN;
    data.pleaseStop ← TRUE;
    PupSocketKick[data.bootSoc];
    PupSocketKick[data.echoSoc];
    PupSocketKick[data.gcSoc];
    PupSocketKick[data.nameSoc];
    PupSocketKick[data.routeSoc];
    PupSocketKick[data.timeSoc];
    WHILE data.activeProcesses # 0 DO Process.Pause[1]; ENDLOOP;
    PupDefs.DestroyPool[pool];
    END;

  Boot: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    bootSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← BootServerDefs.bootStatsRequest;
      SetPupContentsWords[b, 0];
      bootSoc.put[b];
      UNTIL (b ← bootSoc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = BootServerDefs.bootStatsReply)
            AND (body.pupID.a = packetNumber AND body.pupID.b = packetNumber) =>
            BEGIN
            bse: LONG POINTER TO BootServerDefs.BootStatsEntry =
              LOOPHOLE[@body.pupBody];
            data.fastBoot ← PupWireFormat.BcplToMesaLongNumber[bse.fastSends];
            data.slowBoot ← PupWireFormat.BcplToMesaLongNumber[bse.slowSends];
            data.newBoot ← PupWireFormat.BcplToMesaLongNumber[bse.filesRecv];
            END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[bootSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Echo: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.echoSoc;
    echoSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← PupEchoServerDefs.echoStatsRequest;
      SetPupContentsWords[b, 0];
      echoSoc.put[b];
      UNTIL (b ← echoSoc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = PupEchoServerDefs.echoStatsReply)
            AND (body.pupID.a = packetNumber AND body.pupID.b = packetNumber) =>
            BEGIN
            ese: LONG POINTER TO PupEchoServerDefs.EchoStatsEntry =
              LOOPHOLE[@body.pupBody];
            data.echo ← PupWireFormat.BcplToMesaLongNumber[ese.pupsEchoed];
            END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[echoSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Name: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    nameSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← NameServerDefs.nameStatsRequest;
      SetPupContentsWords[b, 0];
      nameSoc.put[b];
      UNTIL (b ← nameSoc.get[]) = NIL DO
        body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = NameServerDefs.nameStatsReply)
            AND (body.pupID.a = packetNumber AND body.pupID.b = packetNumber) =>
            BEGIN
            nse: LONG POINTER TO NameServerDefs.NameStatsEntry =
              LOOPHOLE[@body.pupBody];
            data.name ← PupWireFormat.BcplToMesaLongNumber[nse.nameRequests];
            data.dirsSent ← PupWireFormat.BcplToMesaLongNumber[
              nse.directoriesSend];
            END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[nameSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Timer: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    timeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← PupTimeServerFormat.timeStatsRequest;
      SetPupContentsWords[b, 0];
      timeSoc.put[b];
      UNTIL (b ← timeSoc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = PupTimeServerFormat.timeStatsReply)
            AND (body.pupID.a = packetNumber AND body.pupID.b = packetNumber) =>
            BEGIN
            tse: LONG POINTER TO PupTimeServerFormat.TimeStatsEntry =
              LOOPHOLE[@body.pupBody];
            data.time ←
              PupWireFormat.BcplToMesaLongNumber[tse.tenexRequests] +
                PupWireFormat.BcplToMesaLongNumber[tse.stringRequests] +
                PupWireFormat.BcplToMesaLongNumber[tse.altoRequests];
            END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[timeSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Route: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.gatewaySoc;
    routeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← ForwarderDefs.forwarderStatsRequest;
      SetPupContentsWords[b, 0];
      routeSoc.put[b];
      UNTIL (b ← routeSoc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = ForwarderDefs.forwarderStatsReply)
            AND (body.pupID.a = packetNumber AND body.pupID.b = packetNumber) =>
            BEGIN OPEN ForwarderDefs;
            fse: LONG POINTER TO ForwardStatsEntry = LOOPHOLE[@body.pupBody];
            l: CARDINAL ← PupDefs.GetPupContentsBytes[b]/2;
            n: CARDINAL ← MIN[fse.numberOfNetworks, l - SIZE[ForwardStatsEntry]];
            p: CARDINAL ← (l - n - SIZE[ForwardStatsEntry])/SIZE[TransitMatrixEntry];
            data.route ← PupWireFormat.BcplToMesaLongNumber[
              fse.routingInfoRequests];
            PrintForwardingStats[
              nets: DESCRIPTOR[@body.pupBody + SIZE[ForwardStatsEntry], n],
              pupStatsTable: DESCRIPTOR[
              @body.pupBody + SIZE[ForwardStatsEntry] + n, p]];
            END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[routeSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  PrintForwardingStats: ENTRY PROCEDURE [
    nets: LONG DESCRIPTOR FOR ARRAY OF CARDINAL,
    pupStatsTable: LONG DESCRIPTOR FOR ARRAY OF ForwarderDefs.TransitMatrixEntry] =
    BEGIN
    FindFirst: INTERNAL PROCEDURE [from, to: CARDINAL] =
      BEGIN
      tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry;
      from ← from MOD 400B;
      to ← to MOD 400B;
      FOR i: CARDINAL IN [0..LENGTH[pupStatsTable]) DO
        tme ← @pupStatsTable[i];
        IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
        LD10Dash[PupWireFormat.BcplToMesaLongNumber[tme.count]];
        EXIT;
        REPEAT FINISHED => LD10Dash[0];
        ENDLOOP;
      END;
    FindSecond: INTERNAL PROCEDURE [from, to: CARDINAL] =
      BEGIN
      i: CARDINAL;
      tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry;
      from ← from MOD 400B;
      to ← to MOD 400B;
      FOR i ← 0, i + 1 UNTIL i = LENGTH[pupStatsTable] DO
        tme ← @pupStatsTable[i];
        IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
        EXIT;  -- Skip first matching entry
        REPEAT FINISHED => BEGIN LD10Dash[0]; RETURN; END;
        ENDLOOP;
      FOR i ← i + 1, i + 1 UNTIL i = LENGTH[pupStatsTable] DO
        tme ← @pupStatsTable[i];
        IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
        LD10Dash[PupWireFormat.BcplToMesaLongNumber[tme.count]/1000];
        EXIT;
        REPEAT FINISHED => LD10Dash[0];
        ENDLOOP;
      END;
    SetLinePointer[6];
    WriteLine["Packets forwarded:"L];
    WriteLine["from                to"L];
    WriteString["      Discard"L];
    FOR to: CARDINAL IN [0..LENGTH[nets]) DO
      WriteString["      "L]; O4[nets[to]]; ENDLOOP;
    WriteCR[];
    FOR from: CARDINAL IN [0..LENGTH[nets]) DO
      O4[nets[from]];
      FindFirst[nets[from], 0];
      FOR to: CARDINAL IN [0..LENGTH[nets]) DO
        FindFirst[nets[from], nets[to]]; ENDLOOP;
      WriteCR[];
      ENDLOOP;
    WriteCR[];
    WriteLine["KBytes forwarded:"L];
    WriteLine["from                to"L];
    WriteString["      Discard"L];
    FOR to: CARDINAL IN [0..LENGTH[nets]) DO
      WriteString["      "L]; O4[nets[to]]; ENDLOOP;
    WriteCR[];
    FOR from: CARDINAL IN [0..LENGTH[nets]) DO
      O4[nets[from]];
      FindSecond[nets[from], 0];
      FOR to: CARDINAL IN [0..LENGTH[nets]) DO
        FindSecond[nets[from], nets[to]]; ENDLOOP;
      WriteCR[];
      ENDLOOP;
    END;

  GateControl: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    text: STRING = [50];
    String.AppendString[text, target];
    String.AppendString[text, " ["L];
    AppendPupAddress[text, him];
    String.AppendString[text, "]"L];
    where.socket ← [31415, 9265];
    gcSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← 27182;
      body.pupID.b ← (packetNumber ← packetNumber + 1);
      body.pupType ← GateControlDefs.gateControlStatsSend;
      SetPupContentsWords[b, 0];
      gcSoc.put[b];
      UNTIL (b ← gcSoc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = GateControlDefs.gateControlStatsAck)
            AND (body.pupID.b = packetNumber) => PrintFirstHalf[text, b];
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b]
      ENDLOOP;
    PupSocketDestroy[gcSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  PrintFirstHalf: ENTRY PROCEDURE [text: LONG STRING, b: PupBuffer] =
    BEGIN
    gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry;
    gse ← LOOPHOLE[@b.pup.pupBody];
    SetLinePointer[0];
    WriteString[text];
    PrintUpTime[gse.startTime];
    WriteCR[];
    FOR i: CARDINAL IN [0..gse.versionText.length) DO
      WriteChar[gse.versionText.char[i]]; ENDLOOP;
    WriteCR[];
    WriteCR[];
    PrintItem[data.fastBoot, "Boot: "L];
    PrintItem[data.echo, "Echo: "L];
    PrintItem[data.name, "Name: "L];
    PrintItem[data.route, "Route: "L];
    PrintItem[data.time, "Time: "L];
    WriteCR[];
    IF (data.slowBoot + data.newBoot + data.dirsSent) # 0 THEN
      BEGIN
      PrintItem[data.slowBoot, "SlowBoot: "L];
      PrintItem[data.newBoot, "NewBoot: "L];
      PrintItem[data.dirsSent, "NetDirsSent: "L];
      WriteCR[];
      END;
    PrintItem[gse.freeBuffers, "Buffers: "L];
    PrintItem[gse.freeDiskPages, "Disk pages: "L];
    WriteCR[];
    END;

  PrintUpTime: INTERNAL PROCEDURE [startTime: PupWireFormat.BcplLongNumber] =
    BEGIN
    now, then: LONG INTEGER;
    sec: LONG INTEGER;
    min: LONG INTEGER;
    hours: LONG INTEGER;
    WriteString[" up "L];
    then ← PupWireFormat.BcplToMesaLongNumber[startTime];
    now ← Time.Current[];
    sec ← now - then;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    WriteLongDecimal[hours];
    WriteChar[':];
    IF min < 10 THEN WriteChar['0];
    WriteLongDecimal[min];
    WriteChar[':];
    IF sec < 10 THEN WriteChar['0];
    WriteLongDecimal[sec];
    END;

  PrintItem: INTERNAL PROCEDURE [d: LONG CARDINAL, s: LONG STRING] =
    BEGIN
    IF d = 0 THEN RETURN;
    WriteString[s];
    WriteLongDecimal[d];
    WriteString["  "L];
    END;


  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    text: STRING = [100];
    String.AppendString[text, "Looking at "L];
    String.AppendString[text, data.target];
    String.AppendString[text, "="L];
    GetPupAddress[
      @data.him, data.target !
      PupNameTrouble =>
        BEGIN
        String.AppendString[text, e];
        MsgSW.Post[msg, text];
        GOTO Trouble;
        END];
    AppendPupAddress[text, data.him];
    MsgSW.Post[msg, text];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  CheckPassword: PROCEDURE RETURNS [ok: BOOLEAN] =
    BEGIN
    person: STRING = [100];
    pwd: STRING = [100];
    machine: STRING ← [100];
    status: Password.Status;
    SaveUserInfo: PROCEDURE [name, password: LONG STRING] =
      BEGIN
      String.AppendString[person, name];
      String.AppendString[pwd, password];
      END;
    ok ← TRUE;
    IF ~data.running THEN
      BEGIN
      MsgSW.Post[msg, "Not looking at a target yet"L];
      RETURN[FALSE];
      END;
    Profile.GetUser[SaveUserInfo, registry];
    AppendHostName[machine, data.him];  -- target might have been edited
    String.AppendString[machine, ".internet"L];
    status ← Password.ValidMemberOfGroup[person, pwd, machine !
      Runtime.UnboundProcedure =>
        BEGIN
	MsgSW.Post[msg, "Password checker and/or GrapevineUser not loaded"L];
	ok ← FALSE;
	CONTINUE;
	END];
    IF ~ok THEN RETURN;
    SELECT status FROM
      yes => RETURN[TRUE];
      nil => MsgSW.Post[msg, "Confusion about NIL"L];
      allDown => MsgSW.Post[msg, "All Grapevine servers appear to be down"L];
      notFound => MsgSW.Post[msg, "Grapevine doesn't like your name"L];
      badPwd => MsgSW.Post[msg, "Grapevine doesn't like your password"L];
      group => MsgSW.Post[msg, "Grapevine thinks you are a group"L];
      no =>
        BEGIN
	temp: STRING = [100];
        String.AppendString[temp, "You are not in the appropiate group ("L];
        String.AppendString[temp, machine];
        String.AppendString[temp, ")"L];
	MsgSW.Post[msg, temp];
	END;
      notGroup =>
        BEGIN
	temp: STRING = [100];
        String.AppendString[temp, "Grapevine doesn't recognize this machine's group ("L];
        String.AppendString[temp, machine];
        String.AppendString[temp, ")"L];
	MsgSW.Post[msg, temp];
	END;
      error => MsgSW.Post[msg, "Error from GrapevineUser package"L];
      ENDCASE => ERROR;
    RETURN[FALSE];
    END;

  -- IO things

  SetLinePointer: INTERNAL PROCEDURE [n: [0..maxLines)] = INLINE
    BEGIN data.line ← n; data.lines[data.line].length ← 0; END;

  WriteChar: INTERNAL PROCEDURE [c: CHARACTER] =
    BEGIN String.AppendChar[data.lines[data.line], c]; END;

  WriteCR: INTERNAL PROCEDURE =
    BEGIN
    string: LONG STRING = data.lines[data.line];
    tail: Window.Place;
    tail ← Display.Text[
      window: info, string: string, place: [indentation, data.line*data.height],
      flags: Display.replaceFlags ];
    Display.White[info, [tail, [rightEdge - tail.x, data.height]]];
    data.line ← data.line + 1;
    IF data.line = maxLines THEN data.line ← maxLines - 1;
    data.lines[data.line].length ← 0;
    END;

  WriteString: INTERNAL PROCEDURE [s: LONG STRING] =
    BEGIN i: CARDINAL; FOR i IN [0..s.length) DO WriteChar[s[i]]; ENDLOOP; END;

  WriteLine: INTERNAL PROCEDURE [s: LONG STRING] =
    BEGIN WriteString[s]; WriteCR[]; END;

  WriteLongDecimal: INTERNAL PROCEDURE [n: LONG CARDINAL] =
    BEGIN
    s: STRING = [20];
    String.AppendLongNumber[s, n, 10];
    WriteString[s];
    END;

  LD10: INTERNAL PROCEDURE [n: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    String.AppendLongNumber[s, n, 10];
    THROUGH [s.length..10) DO WriteChar[' ]; ENDLOOP;
    WriteString[s];
    END;

  LD10Dash: INTERNAL PROCEDURE [n: LONG INTEGER] =
    BEGIN IF n = 0 THEN WriteString["        - "L] ELSE LD10[n]; END;

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

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

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

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

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

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

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

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

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

  ShowErrorPup: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    text: STRING = [100];
    PupDefs.AppendErrorPup[text, b];
    MsgSW.Post[msg, text];
    END;

  ClearThings: PROCEDURE =
    BEGIN
    FOR i: CARDINAL IN [0..maxLines) DO data.lines[i].length ← 0; ENDLOOP;
    RepaintThings[];
    END;

  indentation: CARDINAL = 10;
  rightEdge: CARDINAL = 600;
  RepaintThings: PROCEDURE =
    BEGIN
    FOR i: CARDINAL IN [0..maxLines) DO
      tail: Window.Place;
      tail ← Display.Text[
        window: info, string: data.lines[i], place: [indentation, i*data.height],
	flags: Display.replaceFlags ];
      Display.White[info, [tail, [rightEdge - tail.x, data.height]]];
      ENDLOOP;
    END;

  DisplayInfo: ToolWindow.DisplayProcType = BEGIN RepaintThings[]; END;

  ZapCounters: PROCEDURE =
    BEGIN
    data.fastBoot ← data.slowBoot ← data.newBoot ← data.echo ← data.name ←
      data.dirsSent ← data.route ← data.time ← 0;
    END;

  HaltRestart: PROCEDURE [what: {halt, restart}] =
    BEGIN
    soc: PupSocket;
    body: PupDefs.Body;
    sequenceNumber: CARDINAL ← Inline.LowHalf[System.GetClockPulses[]];
    where: PupAddress ← data.him;
    where.socket ← [31415, 9265];
    soc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    THROUGH [0..10) DO
      b: PupBuffer ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← 27182;
      body.pupID.b ← sequenceNumber;
      body.pupType ← SELECT what FROM
        halt => GateControlDefs.gateControlHalt,
        restart => GateControlDefs.gateControlRestart,
	ENDCASE => ERROR;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        -- Until timeout, or we find the expected one
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error) => ShowErrorPup[b];
          (body.pupType = GateControlDefs.gateControlStatsAck)
            AND (body.pupID.b = sequenceNumber) =>
	      BEGIN
	      MsgSW.Post[msg, "Ok"L];
	      GOTO Hit;
	      END;
          ENDCASE => BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
        PupDefs.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN PupDefs.ReturnBuffer[b];
      REPEAT
      Hit => NULL;
      FINISHED => MsgSW.Post[msg, "No response"L];
      ENDLOOP;
    PupSocketDestroy[soc];
    END;




  Start: FormSW.ProcType = BEGIN GateWatcherOn[]; END;

  Stop: FormSW.ProcType =
    BEGIN
    GateWatcherOff[];
    Disable[];
    END;

  Enable: FormSW.ProcType =
    BEGIN
    IF ~CheckPassword[] THEN RETURN;
    FormSW.FindItem[form, haltIX].flags.invisible ← FALSE;
    FormSW.FindItem[form, restartIX].flags.invisible ← FALSE;
    FormSW.Display[form];
    END;
  
  Disable: PROCEDURE =
    BEGIN
    FormSW.FindItem[form, haltIX].flags.invisible ← TRUE;
    FormSW.FindItem[form, restartIX].flags.invisible ← TRUE;
    FormSW.Display[form];
    END;

  Halt: FormSW.ProcType =
    BEGIN
    HaltRestart[halt];
    END;

  Restart: FormSW.ProcType =
    BEGIN
    HaltRestart[restart];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    MakeInfoSW[window];
    END;

  MakeInfoSW: PROCEDURE [window: Window.Handle] =
    BEGIN
    box: Window.Box ← ToolWindow.nullBox;
    box.dims.h ← 20*36;
    info ← ToolWindow.CreateSubwindow[parent: window, display: DisplayInfo, box: box];
    Tool.AddThisSW[window: window, sw: info, swType: vanilla];
    END;

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  enableIX: CARDINAL = 2;
  haltIX: CARDINAL = 3;
  restartIX: CARDINAL = 4;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 6;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[startIX] ← FormSW.CommandItem[
      tag: "Start"L, proc: Start, place: FormSW.newLine];
    items[stopIX] ← FormSW.CommandItem[
      tag: "Stop"L, proc: Stop, place: FormSW.newLine, invisible: TRUE];
    items[enableIX] ← FormSW.CommandItem[tag: "Enable"L, proc: Enable, invisible: TRUE];
    items[haltIX] ← FormSW.CommandItem[tag: "Halt"L, proc: Halt, invisible: TRUE];
    items[restartIX] ← FormSW.CommandItem[tag: "Restart"L, proc: Restart, invisible: TRUE];
    items[5] ← FormSW.StringItem[tag: "Target"L, string: @data.target, inHeap: TRUE];
    RETURN[items, TRUE];
    END;

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

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
        IF data # NIL THEN ERROR AlreadyActive;
        data ← z.NEW[Data];
        data↑ ← [];
        data.height ← WindowFont.FontHeight[];
        data.target ← z.NEW[StringBody[20]];
        String.AppendString[data.target, "ME"L];
        FOR i: CARDINAL IN [0..maxLines) DO
          data.lines[i] ← z.NEW[StringBody[150]]; ENDLOOP;
        [] ← PupDefs.PupPackageMake[];
        END;
      new = inactive =>
        BEGIN
        IF data = NIL THEN ERROR NotActive;
        msg ← form ← info ← NIL;
        IF data.running THEN GateWatcherOff[];
        PupDefs.PupPackageDestroy[];
        FOR i: CARDINAL IN [0..maxLines) DO
          z.FREE[@data.lines[i]]; ENDLOOP;
        z.FREE[@data.target];
        z.FREE[@data];
        END;
      ENDCASE;
    END;


  -- Main Body
  Init[];
  END.