-- Transport Mechanism: User: sending mail

-- [Juniper]<Grapevine>User>SendMail.mesa

-- Andrew Birrell  12-Nov-80 18:29:02

DIRECTORY
BodyDefs	USING [ItemType, maxRNameLength, Password, RName],
LocateDefs	USING [FindNearestServer, FoundServerInfo],
ProtocolDefs	USING [CreateStream, DestroyStream, Failed, Handle,
		       mailServerInputSocket, MakeKey, ReceiveAck,
		       ReceiveBoolean, ReceiveByte, ReceiveCount,
		       ReceiveRName, SendBoolean, SendCount,
		       SendMSOperation, SendNow, SendPassword, SendRName],
PupDefs		USING [PupAddress],
PupStream	USING [StreamClosing],
SendDefs	USING [ExpandInfo, StartSendInfo],
Storage		USING [Free, Node],
Stream		USING [Handle, PutBlock];

SendMail: MONITOR
IMPORTS LocateDefs, ProtocolDefs, PupStream, Stream, Storage
EXPORTS SendDefs =

BEGIN

Handle: PUBLIC TYPE = POINTER TO HandleObject;

HandleObject: TYPE = RECORD[
   state: {idle, started, noItem, inItem},
   validate: BOOLEAN,
   str:   ProtocolDefs.Handle ];

Create: PUBLIC PROC RETURNS[handle: Handle] =
   BEGIN
   handle ← Storage.Node[SIZE[HandleObject]];
   handle↑ ← [state: idle, validate:, str: NIL];
   END;

Destroy: PUBLIC ENTRY PROC[handle: Handle] =
   { Close[handle]; Storage.Free[handle]};

SendFailed: PUBLIC ERROR[notDelivered: BOOLEAN] = CODE;

WrongCallSequence: ERROR = CODE;


-- cached server address --
serverKnown: BOOLEAN ← FALSE;
serverAddr:  PupDefs.PupAddress;

StartSend: PUBLIC PROC[handle: Handle,
                       senderPwd: STRING,
                       sender:    BodyDefs.RName,
                       returnTo:  BodyDefs.RName ← NIL,
                       validate:  BOOLEAN ]
              RETURNS[ info: SendDefs.StartSendInfo ] =
   BEGIN
   info ← SendFromClient[handle,
                         0, 0,
                         ProtocolDefs.MakeKey[senderPwd],
                         sender,
                         IF returnTo = NIL THEN sender ELSE returnTo,
                         validate];
   END;


SendFromClient: PUBLIC ENTRY PROC[handle: Handle,
                                  fromNet: [0..256), fromHost: [0..256),
                                  senderKey: BodyDefs.Password,
                                  sender:    BodyDefs.RName,
                                  returnTo:  BodyDefs.RName,
                                  validate:  BOOLEAN ]
                         RETURNS[ info: SendDefs.StartSendInfo ] =
   BEGIN
   ENABLE UNWIND => Close[handle];
   IF handle.state # idle THEN ERROR WrongCallSequence[];
   DO handle.str ← InnerGetStream[];
      IF handle.str=NIL THEN { info ← allDown; EXIT };
      BEGIN
         ENABLE ProtocolDefs.Failed => GOTO wentDown;
         ProtocolDefs.SendMSOperation[handle.str, startSend];
         ProtocolDefs.SendRName[handle.str, sender];
         ProtocolDefs.SendPassword[handle.str, senderKey, [0,0,0,0]];
         ProtocolDefs.SendRName[handle.str, returnTo];
         ProtocolDefs.SendBoolean[handle.str, validate];
         ProtocolDefs.SendNow[handle.str];
         info ← LOOPHOLE[ProtocolDefs.ReceiveByte[handle.str]];
      EXITS wentDown => { Close[handle]; serverKnown ← FALSE; LOOP };
      END;
      EXIT
   ENDLOOP;
   IF info = ok THEN handle.state ← started;
   END;

GetStream: ENTRY PROC RETURNS[str: ProtocolDefs.Handle] = INLINE
   { str ← InnerGetStream[] };

InnerGetStream: INTERNAL PROC RETURNS[str: ProtocolDefs.Handle] =
   BEGIN
   str ← NIL;
   IF NOT serverKnown
   THEN GOTO noCache
   ELSE str ← ProtocolDefs.CreateStream[serverAddr !
                                      ProtocolDefs.Failed => GOTO noCache];
   EXITS noCache =>
     BEGIN
     Accept: PROC[addr: PupDefs.PupAddress] RETURNS[BOOLEAN] =
        BEGIN
        addr.socket ← ProtocolDefs.mailServerInputSocket;
        str ← ProtocolDefs.CreateStream[addr !
                             ProtocolDefs.Failed => GOTO no];
        serverKnown ← TRUE; serverAddr ← addr;
        RETURN[TRUE];
        EXITS no => RETURN[FALSE]
        END;
     server: LocateDefs.FoundServerInfo =
                       LocateDefs.FindNearestServer["Maildrop.MS"L, Accept];
     IF server.t # found
     THEN { IF str#NIL THEN ProtocolDefs.DestroyStream[str]; str ← NIL };
     END;
   END;

AddRecipient: PUBLIC ENTRY PROC[handle: Handle, recipient: BodyDefs.RName] =
   BEGIN
   ENABLE
      BEGIN
      ProtocolDefs.Failed => ERROR SendFailed[notDelivered:TRUE];
      UNWIND => Close[handle];
      END;
   IF handle.state # started THEN ERROR WrongCallSequence[];
   ProtocolDefs.SendMSOperation[handle.str, addRecipient];
   ProtocolDefs.SendRName[handle.str, recipient];
   END;

CheckValidity: PUBLIC ENTRY PROC[handle: Handle,
                  notify: PROC[CARDINAL, BodyDefs.RName] ]
      RETURNS[ ok: CARDINAL ] =
   BEGIN
   ENABLE
      BEGIN
      ProtocolDefs.Failed => ERROR SendFailed[notDelivered:TRUE];
      UNWIND => Close[handle];
      END;
   IF handle.state # started THEN ERROR WrongCallSequence[];
   ProtocolDefs.SendMSOperation[handle.str, checkValidity];
   ProtocolDefs.SendNow[handle.str];
   DO n: CARDINAL = ProtocolDefs.ReceiveCount[handle.str];
      bad: BodyDefs.RName = [BodyDefs.maxRNameLength];
      IF n = 0 THEN EXIT;
      ProtocolDefs.ReceiveRName[handle.str, bad];
      notify[n, bad];
   ENDLOOP;
   ok ← ProtocolDefs.ReceiveCount[handle.str];
   handle.state ← noItem;
   END;

StartItem: PUBLIC ENTRY PROC[handle: Handle, type: BodyDefs.ItemType] =
   BEGIN
   ENABLE
      BEGIN
      ProtocolDefs.Failed => ERROR SendFailed[notDelivered:TRUE];
      UNWIND => Close[handle];
      END;
   IF handle.state = inItem THEN handle.state ← noItem;
   IF handle.state # noItem THEN ERROR WrongCallSequence[];
   ProtocolDefs.SendMSOperation[handle.str, startItem];
   ProtocolDefs.SendCount[handle.str, LOOPHOLE[type]];
   handle.state ← inItem; 
   END;

AddToItem: PUBLIC ENTRY PROC[handle: Handle,
                       buffer: DESCRIPTOR FOR PACKED ARRAY OF CHARACTER ] =
   BEGIN
   ENABLE
      BEGIN
      ProtocolDefs.Failed, PupStream.StreamClosing =>
                                        ERROR SendFailed[notDelivered:TRUE];
      UNWIND => Close[handle];
      END;
   IF handle.state # inItem THEN ERROR WrongCallSequence[];
   ProtocolDefs.SendMSOperation[handle.str, addToItem];
   ProtocolDefs.SendCount[handle.str, LENGTH[buffer]];
   Stream.PutBlock[handle.str,
                   [ BASE[buffer], 0, LENGTH[buffer] ],
                   FALSE];
   END;

Send: PUBLIC ENTRY PROC[handle: Handle] =
   BEGIN
   ENABLE UNWIND => Close[handle];
   IF handle.state # inItem THEN ERROR WrongCallSequence[];
   -- SendNow to give better error if connection has gone away --
   ProtocolDefs.SendNow[handle.str !
      ProtocolDefs.Failed => ERROR SendFailed[notDelivered:TRUE]  ];
   BEGIN
      ENABLE ProtocolDefs.Failed => ERROR SendFailed[notDelivered:FALSE];
      ProtocolDefs.SendMSOperation[handle.str, send];
      ProtocolDefs.SendNow[handle.str];
      ProtocolDefs.ReceiveAck[handle.str];
   END;
   Close[handle];
   handle.state ← idle;
   END;

Abort: PUBLIC ENTRY PROC[handle: Handle] =
   { Close[handle] };

Close: INTERNAL PROC[handle: Handle] =
   BEGIN
   IF handle.str # NIL THEN ProtocolDefs.DestroyStream[handle.str];
   handle.str ← NIL;
   handle.state ← idle;
   END;

ExpandFailed: PUBLIC ERROR = CODE;

Expand: PUBLIC PROC[name: BodyDefs.RName, work: PROC[BodyDefs.RName]]
            RETURNS[info: SendDefs.ExpandInfo] =
   BEGIN
   str: ProtocolDefs.Handle = GetStream[];
   IF str = NIL
   THEN info ← allDown
   ELSE BEGIN
        ENABLE 
          BEGIN
          ProtocolDefs.Failed => ERROR ExpandFailed[];
          UNWIND => ProtocolDefs.DestroyStream[str];
          END;
        ProtocolDefs.SendMSOperation[str, expand];
        ProtocolDefs.SendRName[str, name];
        ProtocolDefs.SendNow[str];
        WHILE ProtocolDefs.ReceiveBoolean[str]
        DO n: BodyDefs.RName = [BodyDefs.maxRNameLength];
           ProtocolDefs.ReceiveRName[str, n];
           work[n];
        ENDLOOP;
        info ← LOOPHOLE[ProtocolDefs.ReceiveByte[str]];
        ProtocolDefs.DestroyStream[str];
        END;
   END;

END.