-- Transport Mechanism: User: "hot" module for mail polling --

-- [Juniper]<DMS>MS>RetrievePoll.mesa

-- Andrew Birrell  26-Feb-81 11:55:54 --

DIRECTORY
BodyDefs	USING[ RName ],
Inline		USING[ COPY, HighHalf, LongCOPY, LowHalf ],
NameInfoDefs	USING[ AuthenticateInfo, AuthenticateKey ],
NameInfoSpecialDefs USING[ CleanUp ],
Process		USING[ SecondsToTicks, SetTimeout ],
ProtocolDefs	USING[ mailServerPollingSocket ],
PupDefs		USING[ GetFreePupBuffer, PupAddress, PupBuffer,
		       PupRouterSendThis, PupSocket, PupSocketDestroy,
		       PupSocketKick, PupSocketMake, ReturnFreePupBuffer,
		       SetPupContentsBytes, SetPupContentsWords,
		       veryLongWait ],
PupTypes	USING[ fillInSocketID, miscSrvSoc, userAuthBad,
		       userAuthOk, userAuthReq ],
RetrieveDefs	USING[ MBXState, ServerState ],
RetrieveXDefs	USING[ FindAddress, FindRegistryAndMailboxes, Handle,
		       MBXPtr, noMBX ],
System		USING[ GreenwichMeanTime ],
Time		USING[ Current ];

RetrievePoll: MONITOR LOCKS handle USING handle: RetrieveXDefs.Handle
   IMPORTS Inline, NameInfoDefs, NameInfoSpecialDefs, Process,
           ProtocolDefs, PupDefs, RetrieveXDefs, Time
   EXPORTS RetrieveXDefs =

BEGIN

Copy: PROC[from: POINTER, to: LONG POINTER, nwords: CARDINAL] =
   BEGIN
   -- This procedure is required because PupBuffer's are LONG in Pilot
   -- and Inline.LongCopy doesn't work on Alto I's.
   -- I think this procedure will work with a non-zero MDS base --
   longFrom: LONG POINTER = LONG[from];
   IF Inline.HighHalf[longFrom] = Inline.HighHalf[to]
   THEN -- "to" is in the MDS --
        Inline.COPY[from: from,
                    to: Inline.LowHalf[to-(longFrom-from)],
                    nwords: nwords]
   ELSE -- "to" isn't in MDS, so LongCopy must be OK --
        Inline.LongCOPY[from: longFrom, to: to, nwords: nwords];
   END;

transmitLimit:   CARDINAL = 5 -- max number of transmissions of poll --;
retransmitDelay: CARDINAL = 2 -- seconds bfore re-transmittting --;


SendPollProcess: PUBLIC ENTRY PROCEDURE[handle: RetrieveXDefs.Handle] =
   BEGIN -- main program for sending polls --
   socket: PupDefs.PupSocket = PupDefs.PupSocketMake[
              PupTypes.fillInSocketID, , PupDefs.veryLongWait];
   socketAddr: PupDefs.PupAddress = socket.getLocalAddress[];
   replyPoll: PROCESS = FORK PollReplyProcess[handle, socket];
   transmissions: CARDINAL ← 0; -- number of retransmissions so far --
   handle.pollReplying ← TRUE;
   Process.SetTimeout[@handle.pollCond,
                          Process.SecondsToTicks[retransmitDelay] ];
   WHILE handle.pollWanted
   DO now: System.GreenwichMeanTime = Time.Current[];
      IF handle.newPollWanted
      OR now >= handle.pollStarted + handle.interval
      THEN BEGIN
           handle.pollStarted ← now;
           transmissions ← 0;
           IF handle.mbxState NOT IN [userOK..notEmpty]
           THEN handle.mbxState ← unknown --retry authentication--
           ELSE BEGIN
                IF handle.notEmptyMBXCount = 0
                AND handle.unknownMBXCount # 0
                THEN handle.mbxState ← userOK --was someEmpty or allDown--;
                FOR this: RetrieveXDefs.MBXPtr ← handle.MBXChain, this.next
                WHILE this # RetrieveXDefs.noMBX
                DO IF this.addrState = unknown
                   THEN RetrieveXDefs.FindAddress[handle, this];
                   IF this.addrState = known
                   AND (handle.newPollWanted OR this.state # notEmpty)
                   THEN this.replyWanted ← TRUE;
                ENDLOOP;
                END;
           handle.newPollWanted ← FALSE;
           BROADCAST handle.mbxStateChange;
           END;
      IF transmissions >= transmitLimit
      THEN BEGIN
           Process.SetTimeout[@handle.pollCond,
              Process.SecondsToTicks[Inline.LowHalf[
                 (handle.pollStarted + handle.interval) - now] ] ];
           WAIT handle.pollCond;
           Process.SetTimeout[@handle.pollCond,
                             Process.SecondsToTicks[retransmitDelay] ];
           END
      ELSE SELECT handle.mbxState FROM
             unknown =>
                BEGIN -- authenticate --	
                IF NOT handle.mbxKnown
                THEN RetrieveXDefs.FindRegistryAndMailboxes[handle];
                IF handle.mbxState = unknown
                THEN IF handle.registry = MTP
                     THEN BEGIN
                          SendAuthReq[handle, socketAddr];
                          transmissions ← transmissions + 1;
                          WAIT handle.pollCond--wait for reply--;
                          IF transmissions >= transmitLimit
                          THEN BEGIN
                               IF handle.mbxState = unknown --no reply--
                               THEN SetMBXState[handle, cantAuth];
                               END;
                          END
                     ELSE BEGIN
                          info: NameInfoDefs.AuthenticateInfo =
                              NameInfoDefs.AuthenticateKey[handle.userName,
                                                           handle.userKey];
                          SetMBXState[handle,
                                      SELECT info FROM
                                        individual =>      userOK,
                                        badPwd =>          badPwd,
                                        group, notFound => badName,
                                        allDown =>         cantAuth,
                                      ENDCASE => ERROR];
                          END
                --ELSE "FindRegistryAndMailboxes" failed--;
                END;
             IN [userOK..notEmpty] => --authenticated--
                BEGIN -- poll the mailboxes --
                finished: BOOLEAN ← TRUE; --whether all have replied--
                transmissions ← transmissions + 1;
                FOR this: RetrieveXDefs.MBXPtr ← handle.MBXChain, this.next
                WHILE this # RetrieveXDefs.noMBX
                DO IF this.replyWanted
                   THEN BEGIN
                        b: PupDefs.PupBuffer = PupDefs.GetFreePupBuffer[];
                        Copy[from: @(handle.userName.text),
                             to: @(b.pupString),
                             nwords: (1+handle.userName.length)/2 ];
                        PupDefs.SetPupContentsBytes[b, handle.userName.length];
                        b.pupType ← mailCheckLaurel;
                        b.pupID ← handle.pollID;
                        b.dest ← [this.addr.net, this.addr.host,
                                IF this.type = MTP
                                THEN PupTypes.miscSrvSoc
                                ELSE ProtocolDefs.mailServerPollingSocket];
                        b.source ← socketAddr;
                        PupDefs.PupRouterSendThis[b];
                        finished ← FALSE;
                        END;
                ENDLOOP;
                IF finished
                THEN transmissions ← transmitLimit -- all have replied --
                ELSE WAIT handle.pollCond;
                IF transmissions >= transmitLimit
                THEN BEGIN
                     FOR this: RetrieveXDefs.MBXPtr ←
                                                 handle.MBXChain, this.next
                     WHILE this # RetrieveXDefs.noMBX
                     DO IF this.addrState = unknown OR this.replyWanted
                        THEN NoteChangedMBX[handle, this, unknown];
                     ENDLOOP;
                     -- special case for user with no mailboxes --
                     IF handle.MBXChain = RetrieveXDefs.noMBX
                     THEN SetMBXState[handle, allEmpty];
                     NameInfoSpecialDefs.CleanUp[];
                     END;
                END;
           ENDCASE => -- couldn't authenticate --
             transmissions ← transmitLimit;
   ENDLOOP;
   PupDefs.PupSocketKick[socket];
   WHILE handle.pollReplying DO WAIT handle.pollCond ENDLOOP;
   JOIN replyPoll;
   PupDefs.PupSocketDestroy[socket];
   handle.polling ← FALSE; NOTIFY handle.pollCond;
   END;


SendAuthReq: INTERNAL PROC[handle: RetrieveXDefs.Handle,
                           socketAddr: PupDefs.PupAddress] =
   BEGIN
   this: RetrieveXDefs.MBXPtr = handle.MBXChain;
   IF this.type # MTP THEN ERROR;
   IF this.addrState # known
   THEN SetMBXState[handle, cantAuth]
   ELSE BEGIN
        b: PupDefs.PupBuffer = PupDefs.GetFreePupBuffer[];
        userWords: CARDINAL = SIZE[StringBody[handle.userName.maxlength]];
        pwdWords: CARDINAL = SIZE[StringBody[handle.userPwd.maxlength]];
        Copy[from: handle.userName, to: @(b.pupBody), nwords: userWords ];
        Copy[from: handle.userPwd, to: @(b.pupBody)+userWords,
             nwords: pwdWords ];
        PupDefs.SetPupContentsWords[b, userWords + pwdWords];
        b.pupType ← PupTypes.userAuthReq;
        b.dest ← [this.addr.net, this.addr.host, PupTypes.miscSrvSoc];
        b.pupID ← handle.pollID;
        b.source ← socketAddr;
        PupDefs.PupRouterSendThis[b];
        END;
   END;


PollReplyProcess: PROCEDURE[ handle: RetrieveXDefs.Handle,
                             socket: PupDefs.PupSocket ] =
   BEGIN -- main program for replies to the polls --
   DO BEGIN
      b: PupDefs.PupBuffer = socket.get[];
      IF NOT ConsiderPollReply[handle, b] THEN EXIT;
      IF b # NIL THEN PupDefs.ReturnFreePupBuffer[b];
      END;
   ENDLOOP;
   END;

ConsiderPollReply: ENTRY PROCEDURE[handle: RetrieveXDefs.Handle,
                                   b: PupDefs.PupBuffer]
                           RETURNS[BOOLEAN] =
   BEGIN
   IF NOT handle.pollWanted
   THEN BEGIN
        handle.pollReplying ← FALSE; BROADCAST handle.pollCond;
        RETURN[FALSE]
        END;
   IF b # NIL AND b.pupID = handle.pollID
   THEN BEGIN
        mbx: RetrieveXDefs.MBXPtr;
        FOR mbx ← handle.MBXChain, mbx.next UNTIL mbx = RetrieveXDefs.noMBX
        DO IF mbx.addrState = known
           AND mbx.addr.net = b.source.net
           AND mbx.addr.host = b.source.host
           THEN BEGIN
                noProcessPupErrorCode: CARDINAL = 2B;
                cantGetTherePupErrorCode: CARDINAL = 1002B;
                eightHopsPupErrorCode: CARDINAL = 1004B;
	      SELECT b.pupType FROM
                  PupTypes.userAuthOk => -- MTP authentication --
                    BEGIN
                    mbx.replyWanted ← TRUE;
                    IF handle.mbxState = unknown
                    THEN SetMBXState[handle, userOK];
                    handle.pollStarted ← 0; --force new poll for mbx's --
                    END;
                  PupTypes.userAuthBad =>
                    IF handle.mbxState = unknown
                    THEN SetMBXState[handle, badPwd--badName?--];
                  mailIsNew =>
                    NoteChangedMBX[handle, mbx, notEmpty];
                  mailNotNew =>
                    NoteChangedMBX[handle, mbx, empty];
                  mailError =>
                    NoteChangedMBX[handle, mbx, empty];
                  error =>
		 -- The following LOOPHOLE brought to you courtesy of the Alto version
		 -- of PupTypes has PupErrorCode: TYPE = RECORD[WORD] and the Pilot
		 -- version has PupErrorCode: TYPE = MACHINE DEPENDENT { .... }.
                    SELECT LOOPHOLE[b.errorCode, CARDINAL] FROM
                      noProcessPupErrorCode,
                      cantGetTherePupErrorCode,
                      eightHopsPupErrorCode =>
                        NoteChangedMBX[handle, mbx, unknown];
                    ENDCASE => NULL;
                ENDCASE => NULL;
                END;
        ENDLOOP;
        END;
   RETURN[TRUE]
   END;


NoteChangedMBX: PUBLIC INTERNAL PROCEDURE[handle: RetrieveXDefs.Handle,
                         mbx: RetrieveXDefs.MBXPtr,
                         new: RetrieveDefs.ServerState] =
   BEGIN
   mbx.replyWanted ← FALSE;
   BROADCAST handle.mbxStateChange;
   IF new # mbx.state
   THEN SELECT new FROM
          unknown =>
            BEGIN
            IF mbx.state = notEmpty
            THEN handle.notEmptyMBXCount ← handle.notEmptyMBXCount - 1;
            handle.unknownMBXCount ← handle.unknownMBXCount + 1;
            END;
          empty =>
            BEGIN
            SELECT mbx.state FROM
              unknown => handle.unknownMBXCount ← handle.unknownMBXCount - 1;
              notEmpty => handle.notEmptyMBXCount ← handle.notEmptyMBXCount - 1;
            ENDCASE => NULL;
            END;
          notEmpty =>
            BEGIN
            IF mbx.state = unknown
            THEN handle.unknownMBXCount ← handle.unknownMBXCount - 1;
            handle.notEmptyMBXCount ← handle.notEmptyMBXCount + 1;
            END;
        ENDCASE => ERROR;
   mbx.state ← new;
   IF new = unknown
   THEN -- if server is down, its address may change! --
        mbx.addrState ← unknown;
   BEGIN
      -- consider whether poll is complete --
      complete: BOOLEAN ← TRUE;
      emptyFound: BOOLEAN ← FALSE;
      FOR this: RetrieveXDefs.MBXPtr ← handle.MBXChain, this.next
      WHILE this # RetrieveXDefs.noMBX
      DO IF this.state = empty THEN emptyFound ← TRUE;
         IF this.replyWanted THEN complete ← FALSE;
      ENDLOOP;
      -- definitive calculation of global state! --
      SetMBXState[ handle, SELECT TRUE FROM
                             (handle.notEmptyMBXCount # 0) => notEmpty,
                             (handle.unknownMBXCount = 0) => allEmpty,
                             (NOT complete) => userOK,
                             emptyFound => someEmpty
                           ENDCASE => allDown ];
   END;
   END;

SetMBXState: PUBLIC INTERNAL PROCEDURE[handle: RetrieveXDefs.Handle,
                                       state: RetrieveDefs.MBXState] =
   BEGIN
   BROADCAST handle.mbxStateChange;
   IF state # userOK AND handle.changes # NIL
   THEN handle.changes[state];
   handle.mbxState ← state;
   END;



END.