-- Copyright (C) 1981, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- SendMail.mesa: User: sending mail

-- HGM, 15-Sep-85  5:48:33
-- Mark Johnson    10-Dec-81 10:33:17
-- Andrew Birrell  30-Mar-81 13:44:59

DIRECTORY
  BodyDefs USING [ItemType, maxRNameLength, Password, RName],
  Heap USING [systemZone],
  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],
  Stream USING [Handle, PutBlock];

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

  BEGIN

  Handle: PUBLIC TYPE = LONG POINTER TO HandleObject;

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

  Create: PUBLIC PROC RETURNS [handle: Handle] =
    BEGIN
    handle ← Heap.systemZone.NEW[HandleObject];
    handle↑ ← [state: idle, str: NIL];
    END;

  Destroy: PUBLIC ENTRY PROC [handle: Handle] = {
    Close[handle]; Heap.systemZone.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: LONG 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 = started THEN handle.state ← inItem;
    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: LONG DESCRIPTOR FOR PACKED ARRAY OF CHARACTER] =
    BEGIN
    ENABLE
      BEGIN
      ProtocolDefs.Failed, PupStream.StreamClosing =>
        ERROR SendFailed[notDelivered: TRUE];
      UNWIND => Close[handle];
      END;
    bufferAddr: LONG POINTER = BASE[buffer];
    IF handle.state # inItem THEN ERROR WrongCallSequence[];
    ProtocolDefs.SendMSOperation[handle.str, addToItem];
    ProtocolDefs.SendCount[handle.str, LENGTH[buffer]];
    Stream.PutBlock[handle.str, [bufferAddr, 0, LENGTH[buffer]], FALSE];
    END;

  Send: PUBLIC ENTRY PROC [handle: Handle] =
    BEGIN
    ENABLE UNWIND => Close[handle];
    IF handle.state = started THEN handle.state ← inItem;
    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.