-- File: HostWatcherPoke.mesa - last edit:
-- AOF                  3-Feb-88 17:21:54
-- HGM                 25-Nov-84 10:08:11
-- Copyright (C) 1983, 1984, 1988 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Ascii USING [CR, LF, SP],
  Heap USING [systemZone],
  Stream USING [Delete, GetChar, Handle, PutChar, SendNow, TimeOut],
  String USING [
    AppendString, AppendChar, AppendLongDecimal, WordsForString, EquivalentString],
  System USING [gmtEpoch],
  Time USING [Current],

  OldLibrarian USING [PropertyList, PropertyPair, PropertyNumber],
  OldLibrarianPN USING [IDName, StringName],
  OldLibrarianOps USING [LibjectMessage, LibjectMessageType, ValidLibrarianMessage],

  STP USING [Create, Destroy, Error, Handle, Open],

  GateControlDefs USING [
    gateControlStatsSend, gateControlStatsAck, GateControlStatsEntry],
  HostWatcherOps USING [Info, ShowErrorPup, State],
  PopCorn USING [Error, GetClockOffset],
  PupDefs USING [
    Body, PupBuffer, PupSocket, PupSocketDestroy,
    PupSocketMake, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes,
    veryLongWait, AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  PupStream USING [PupByteStreamCreate, StreamClosing],
  PupTypes USING [fillInSocketID, PupAddress, PupType],
  PupWireFormat USING [BcplLongNumber, BcplToMesaLongNumber];

HostWatcherPoke: PROGRAM
  IMPORTS
    Heap, Stream, STP, String, Time, PupWireFormat, PopCorn, PupDefs, PupStream,
    HostWatcherOps
  EXPORTS HostWatcherOps =
  BEGIN OPEN PupDefs, PupTypes;
  
  z: UNCOUNTED ZONE = Heap.systemZone;

  Info: TYPE = HostWatcherOps.Info;

  debug: BOOLEAN ← FALSE;

  PokeGateway: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    pool: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 2];
    soc: PupSocket ← PupSocketMake[fillInSocketID, info.address, SecondsToTocks[5]];
    b: PupBuffer ← NIL;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← NextSequenceNumber[];
    tries: CARDINAL ← 10;
    THROUGH [0..tries) 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];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error AND body.errorCode = noProcessPupErrorCode) =>
            BEGIN
            i: CARDINAL;
            FOR i IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, body.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (body.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          (body.pupType = GateControlDefs.gateControlStatsAck)
            AND (body.pupID.b = packetNumber) =>
            BEGIN
            gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry;
	    oldStartTime: LONG CARDINAL ← info.startTime;
            i: CARDINAL;
            gse ← LOOPHOLE[@body.pupBody];
            FOR i IN [0..gse.versionText.length) DO
              String.AppendChar[info.text, gse.versionText.char[i]]; ENDLOOP;
            AppendUpTime[info.text, gse.startTime];
	    info.startTime ← [PupWireFormat.BcplToMesaLongNumber[gse.startTime]];
	    IF oldStartTime # System.gmtEpoch
	    AND (info.startTime > (oldStartTime + 2*60)) THEN GOTO Restarted;
            GOTO Up;
            END;
          ENDCASE => NULL;
        PupDefs.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
	Restarted => info.state ← restarted;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN PupDefs.ReturnBuffer[b];
    PupSocketDestroy[soc];
    PupDefs.DestroyPool[pool];
    END;

  AppendUpTime: PROCEDURE [
    s: LONG STRING, startTime: PupWireFormat.BcplLongNumber] =
    BEGIN
    now, then: LONG INTEGER;
    sec: LONG INTEGER;
    min: LONG INTEGER;
    hours: LONG INTEGER;
    String.AppendString[s, " up "L];
    then ← PupWireFormat.BcplToMesaLongNumber[startTime];
    now ← Time.Current[];
    sec ← now - then;
    IF sec < 0 THEN
      BEGIN  -- Startup glitch
      sec ← -sec;
      String.AppendChar[s, '-];
      END;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    String.AppendLongDecimal[s, hours];
    String.AppendChar[s, ':];
    IF min < 10 THEN String.AppendChar[s, '0];
    String.AppendLongDecimal[s, min];
    String.AppendChar[s, ':];
    IF sec < 10 THEN String.AppendChar[s, '0];
    String.AppendLongDecimal[s, sec];
    END;

  PokeChat: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    sh: Stream.Handle ← NIL;
    state: HostWatcherOps.State ← up;
    BEGIN
    ENABLE
      BEGIN
      PupStream.StreamClosing =>
        BEGIN
        IF text # NIL THEN String.AppendString[info.text, text];
        SELECT why FROM
          transmissionTimeout => state ← timeout;
          remoteReject => state ← rejecting;
          ENDCASE =>
            BEGIN
            LookAtThis: SIGNAL = CODE;
            IF debug THEN SIGNAL LookAtThis;
            state ← unknown;
            END;
        IF CheckForFull[info.text] THEN state ← full;
        CONTINUE;
        END;
      Stream.TimeOut => {state ← timeout; CONTINUE; };
      END;
    sh ← PupStream.PupByteStreamCreate[info.address, PupDefs.veryLongWait];
    IF sh # NIL THEN
      BEGIN
      Stream.PutChar[sh, Ascii.CR];  -- Krock for Vaxc
      Stream.SendNow[sh];
      DO
        c: CHARACTER;
        c ← Stream.GetChar[sh];
        IF c = Ascii.LF THEN LOOP;
        IF c = Ascii.SP AND info.text.length = 0 THEN LOOP;  -- More Vaxc krockery
        IF c = Ascii.CR THEN {IF info.text.length = 0 THEN LOOP ELSE EXIT; };
        String.AppendChar[info.text, c];
        ENDLOOP;
      END;
    END;
    IF sh # NIL THEN Stream.Delete[sh];
    info.state ← state;
    END;

  CheckForFull: PROCEDURE [text: LONG STRING] RETURNS [full: BOOLEAN] =
    BEGIN
    IF String.EquivalentString[text, "RFC Refused"L] THEN RETURN[TRUE];  -- Alto: FTP.run
    IF String.EquivalentString[text, "Server full, try again later"L] THEN
      RETURN[TRUE];  -- Maxc
    IF String.EquivalentString[text, "No dial-out lines available"L] THEN
      RETURN[TRUE];  -- DLS
    IF String.EquivalentString[text, "Line is busy"L] THEN RETURN[TRUE];  -- DLS, specific port
    IF String.EquivalentString[text, "Server is full"L] THEN RETURN[TRUE];  -- Juniper
    IF String.EquivalentString[text, "IFS is full - try later"L] THEN
      RETURN[TRUE];
    IF String.EquivalentString[text, "Server full"L] THEN RETURN[TRUE];  -- Grapevine
    IF String.EquivalentString[text, "Sorry, we are full now"L] THEN RETURN[TRUE];
    -- Gateway
    RETURN[FALSE];
    END;

  PokeFtp: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    state: HostWatcherOps.State ← up;
    handle: STP.Handle ← NIL;
    herald: LONG STRING ← NIL;
    handle ← STP.Create[];
    herald ← STP.Open[handle, info.name !
      STP.Error =>
        BEGIN
        SELECT code FROM
	  connectionRejected => state ← rejecting;
	  connectionTimedOut => state ← timeout;
	  ENDCASE => state ← unknown;
        String.AppendString[info.text, error];
	CONTINUE;
	END ];
    String.AppendString[info.text, herald];
    z.FREE[@herald];
    info.state ← state;
    [] ← STP.Destroy[handle];
    END;

  PokeMail: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    info.state ← unknown;
    END;

  PokeLibrarian: PUBLIC PROCEDURE [info: Info] =
    BEGIN OPEN OldLibrarian, OldLibrarianPN, OldLibrarianOps;
    pool: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 2];
    soc: PupSocket ← PupSocketMake[fillInSocketID, info.address, SecondsToTocks[15]];
    request: LibjectMessageType = FindID;
    b: PupBuffer;
    body: PupDefs.Body;
    message: LONG POINTER TO LibjectMessage;
    plist: LONG POINTER TO ARRAY [0..2) OF PropertyPair;
    transactionID: CARDINAL ← NextSequenceNumber[];
    FOR i: CARDINAL IN [0..5) DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID ← [transactionID, i];
      message ← LOOPHOLE[@body.pupWords];
      message.id ← transactionID;
      message.password ← ValidLibrarianMessage;
      message.type ← request;
      message.plist ← DESCRIPTOR[NIL, 2];
      plist ← LOOPHOLE[message + SIZE[LibjectMessage]];
      message.nextfree ← LOOPHOLE[SIZE[LibjectMessage] + 2*SIZE[PropertyPair]];
      BEGIN
      COPY: PROCEDURE [from: POINTER, nwords: CARDINAL, to: LONG POINTER] =
        BEGIN
        FOR i: CARDINAL IN [0..nwords) DO (to + i)↑ ← (from + i)↑; ENDLOOP;
        END;
      string: STRING = "HostWatcher"L;
      wordsforstring: CARDINAL = String.WordsForString[string.length];
      dataArea: LONG POINTER ← message + LOOPHOLE[message.nextfree, CARDINAL];
      plist[0] ← PropertyPair[
        empty: FALSE, pn: StringName,
        body: String[
        length: string.length, string: LOOPHOLE[2*SIZE[PropertyPair]]]];
      COPY[from: string, nwords: wordsforstring, to: dataArea];
      message.nextfree ← message.nextfree + wordsforstring;
      END;
      plist[1] ← PropertyPair[empty: TRUE, pn: IDName, body: TwoWord[[lc[0]]]];
      SetPupContentsWords[b, LOOPHOLE[message.nextfree, INTEGER]];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        body ← b.pup;
        message ← LOOPHOLE[@body.pupWords];
        SELECT TRUE FROM
          (body.pupType = error AND body.errorCode = noProcessPupErrorCode) =>
            BEGIN
            i: CARDINAL;
            FOR i IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, body.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (body.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          message.password = ValidLibrarianMessage AND message.id = transactionID
            => GOTO Up;
          ENDCASE => NULL;  -- I wonder what this is
        PupDefs.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN PupDefs.ReturnBuffer[b];
    PupSocketDestroy[soc];
    PupDefs.DestroyPool[pool];
    END;

  PokeSpruce: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    pool: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, info.address, SecondsToTocks[5]];
    spruceStatusRequest: PupType = LOOPHOLE[200B];
    spruceStatusReply: PupType = LOOPHOLE[201B];
    b: PupBuffer ← NIL;
    body: PupDefs.Body;
    packetNumber: CARDINAL ← NextSequenceNumber[];
    THROUGH [0..20) DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← packetNumber;
      body.pupType ← spruceStatusRequest;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = error AND body.errorCode = noProcessPupErrorCode) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, body.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (body.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          ((body.pupType # spruceStatusReply) OR (body.pupID.a # packetNumber)
            OR (body.pupID.b # packetNumber)) => NULL;
          ENDCASE =>
            BEGIN
            FOR i: CARDINAL IN [2..GetPupContentsBytes[b] - 1) DO
              c: CHARACTER ← body.pupChars[i];
              IF c = Ascii.CR THEN c ← Ascii.SP;
              String.AppendChar[info.text, c];
              ENDLOOP;
            SELECT body.pupWords[0] FROM
              1 => GOTO Down;  -- Not Spooling
              2 => GOTO Up;  -- Spooler is idle
              3 => GOTO Up;  -- Spooler is busy
              ENDCASE => NULL;
            GOTO Up;
            END;
        PupDefs.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Down => info.state ← down;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN PupDefs.ReturnBuffer[b];
    PupSocketDestroy[soc];
    PupDefs.DestroyPool[pool];
    END;

  PokeEftp: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    pool: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 2];
    soc: PupSocket ← PupSocketMake[fillInSocketID, info.address, SecondsToTocks[5]];
    b: PupBuffer ← NIL;
    body: PupDefs.Body;
    THROUGH [0..20) DO
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.pupID.a ← body.pupID.b ← 0;
      body.pupType ← eData;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	body ← b.pup;
        SELECT TRUE FROM
          (body.pupType = eAbort) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2) DO
              String.AppendChar[info.text, body.pupChars[i + 2]]; ENDLOOP;
            GOTO Rejecting;
            END;
          (body.pupType = error AND body.errorCode = noProcessPupErrorCode) =>
            BEGIN
            FOR i: CARDINAL IN [0..GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
              String.AppendChar[info.text, body.errorText[i]]; ENDLOOP;
            HostWatcherOps.ShowErrorPup[b];
            GOTO Rejecting;
            END;
          (body.pupType = error) => HostWatcherOps.ShowErrorPup[b];
          ((body.pupType = eAck) AND (body.pupID.a = 0) AND (body.pupID.b = 0)) => GOTO Up;
          ENDCASE => HostWatcherOps.ShowErrorPup[b];
        PupDefs.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      REPEAT
        Rejecting => info.state ← rejecting;
        Up => info.state ← up;
        FINISHED => info.state ← timeout;
      ENDLOOP;
    IF b # NIL THEN PupDefs.ReturnBuffer[b];
    PupSocketDestroy[soc];
    PupDefs.DestroyPool[pool];
    END;

  PokePopCorn: PUBLIC PROCEDURE [info: Info] =
    BEGIN
    msDiff: LONG INTEGER;
    msDelay: LONG CARDINAL;
    info.state ← up;
    [msDiff: msDiff, msDelay: msDelay] ← PopCorn.GetClockOffset[info.address, 2 !
      PopCorn.Error =>
        BEGIN
	String.AppendString[info.text, text];
	SELECT why FROM
	  timeout => info.state ← timeout;
	  rejecting =>
	    BEGIN
	    info.state ← rejecting;
	    IF CheckForFull[text] THEN info.state ← full;
	    END;
	  ENDCASE => info.state ← down;
	CONTINUE;
	END];
    IF info.state = up THEN
      BEGIN
      String.AppendString[info.text, info.name];
      String.AppendString[info.text, "'s time is "L];
      String.AppendLongDecimal[info.text, msDiff];
      String.AppendString[info.text, " ms faster than ours"L];
      String.AppendString[info.text, ", delay was "L];
      String.AppendLongDecimal[info.text, msDelay];
      String.AppendString[info.text, " ms"L];
      END;
    END;

  sequenceNumber: CARDINAL ← 0;
  NextSequenceNumber: PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[sequenceNumber ← sequenceNumber + 1]; END;

  END.