-- File: EFTPSend.mesa,  Last Edit: Levin  9-Mar-82 10:55:50

DIRECTORY
  Heap USING [systemMDSZone],
  Process USING [SetTimeout, MsecToTicks],
  CommUtilDefs USING [CopyLong],
  PupDefs USING [
    DataWordsPerPupBuffer, GetFreePupBuffer, ReturnFreePupBuffer,
    GetPupContentsBytes, SetPupContentsBytes, PupAddress, PupBuffer, PupSocketID,
    PupSocket, PupSocketMake, PupSocketDestroy, SecondsToTocks,
    UniqueLocalPupSocketID],
  PupTypes USING [maxDataBytesPerGatewayPup],
  EFTPDefs USING [EFTPAbortCode],
  DriverDefs USING [Network],
  BufferDefs;

EFTPSend: MONITOR
  IMPORTS Heap, Process, CommUtilDefs, PupDefs
  EXPORTS BufferDefs, EFTPDefs
  SHARES BufferDefs =
  BEGIN OPEN PupDefs, EFTPDefs;

  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = DriverDefs.Network;

  EFTPTimeOut: PUBLIC SIGNAL = CODE;
  EFTPAlreadySending: PUBLIC ERROR = CODE;
  EFTPNotSending: PUBLIC ERROR = CODE;
  EFTPTroubleSending: PUBLIC ERROR [e: EFTPAbortCode, s: STRING] = CODE;

  send: CONDITION;
  senderStop: BOOLEAN;
  senderFork: PROCESS;
  sendSocket: PupSocket ← NIL;
  sendHim: PupAddress;
  sendSeqNumber: CARDINAL;
  sendAbortCode: EFTPAbortCode;
  sendState: {acked, waitingForAck};
  sendErrorText: STRING ← NIL;
  retransmissions: CARDINAL;

  dataBytesPerPup: CARDINAL;

  EFTPSetSendTimeout: PUBLIC PROCEDURE [ms, tries: CARDINAL] =
    BEGIN
    retransmissions ← tries;
    Process.SetTimeout[@send, Process.MsecToTicks[ms]];
    END;

  EFTPOpenForSending: PUBLIC PROCEDURE [who: PupAddress, waitForAck: BOOLEAN] =
    BEGIN
    EFTPOpenCheck[who];
    BEGIN
    ENABLE UNWIND => EFTPKillSending[];
    IF waitForAck THEN EFTPOpenLocked[];
    END;
    END;

  EFTPOpenCheck: ENTRY PROCEDURE [to: PupAddress] =
    BEGIN
    ENABLE UNWIND => NULL;
    me: PupSocketID ← UniqueLocalPupSocketID[];
    IF sendSocket # NIL THEN ERROR EFTPAlreadySending;
    dataBytesPerPup ← MIN[
      2*DataWordsPerPupBuffer[], PupTypes.maxDataBytesPerGatewayPup];
    sendHim ← to;
    sendSocket ← PupSocketMake[me, sendHim, SecondsToTocks[1]];
    sendSeqNumber ← LAST[CARDINAL];
    sendAbortCode ← eftpOK;
    sendState ← acked;
    senderStop ← FALSE;
    senderFork ← FORK EFTPReplyForSender[];
    END;

  EFTPOpenLocked: ENTRY PROCEDURE =
    BEGIN
    ENABLE UNWIND => NULL;
    b: PupBuffer;
    sendState ← waitingForAck;
    sendSeqNumber ← sendSeqNumber + 1;
    UNTIL sendState = acked DO
      THROUGH [0..10) UNTIL sendState = acked DO
	b ← GetFreePupBuffer[];
	b.pupType ← eData;
	b.pupID ← [0, sendSeqNumber];
	SetPupContentsBytes[b, 0];
	sendSocket.put[b];
	WAIT send; -- 10*1 sec

	ENDLOOP;
      IF sendState = acked THEN EXIT;
      SIGNAL EFTPTimeOut;
      ENDLOOP;
    IF sendAbortCode # eftpOK THEN
      BEGIN ERROR EFTPTroubleSending[sendAbortCode, sendErrorText]; END;
    END;

  EFTPSendBlock: PUBLIC ENTRY PROCEDURE [p: LONG POINTER, l: CARDINAL] =
    BEGIN
    ENABLE UNWIND => NULL;
    n: CARDINAL;
    b: PupBuffer;
    IF sendSocket = NIL THEN ERROR EFTPNotSending;
    UNTIL l = 0 DO
      sendSeqNumber ← sendSeqNumber + 1;
      sendState ← waitingForAck;
      n ← MIN[l, dataBytesPerPup];
      DO
	THROUGH [0..retransmissions) UNTIL sendState = acked DO
	  b ← GetFreePupBuffer[];
	  b.pupType ← eData;
	  b.pupID ← [0, sendSeqNumber];
	  CommUtilDefs.CopyLong[from: p, nwords: (n + 1)/2, to: @b.pupBytes];
	  SetPupContentsBytes[b, n];
	  sendSocket.put[b];
	  WAIT send; -- 25*1 sec

	  ENDLOOP;
	IF sendAbortCode # eftpOK THEN
	  BEGIN ERROR EFTPTroubleSending[sendAbortCode, sendErrorText]; END;
	IF sendState = acked THEN EXIT;
	SIGNAL EFTPTimeOut;
	ENDLOOP;
      p ← p + (n + 1)/2;
      l ← l - n;
      ENDLOOP;
    END;

  EFTPAbortSending: PUBLIC PROCEDURE [s: STRING] =
    BEGIN
    IF sendSocket = NIL THEN RETURN;
    EFTPAbortSendingLocked[s];
    EFTPKillSending[];
    END;

  EFTPAbortSendingLocked: ENTRY PROCEDURE [s: STRING] =
    BEGIN
    i, end: CARDINAL;
    b: PupBuffer;
    IF sendAbortCode = eftpOK THEN
      BEGIN
      b ← GetFreePupBuffer[];
      b.pupType ← eAbort;
      b.pupWords[0] ← LOOPHOLE[EFTPAbortCode[eftpExternalSenderAbort]];
      end ← MIN[s.length + 2, dataBytesPerPup];
      FOR i ← 2, i + 1 UNTIL i = end DO b.pupChars[i] ← s[i - 2]; ENDLOOP;
      SetPupContentsBytes[b, end];
      sendSocket.put[b];
      END;
    END;

  EFTPFinishSending: PUBLIC PROCEDURE =
    BEGIN EFTPFinishSendingLocked[]; EFTPKillSending[]; END;

  EFTPFinishSendingLocked: ENTRY PROCEDURE =
    BEGIN
    ENABLE UNWIND => NULL;
    b: PupBuffer;
    IF sendSocket = NIL THEN ERROR EFTPNotSending;
    sendSeqNumber ← sendSeqNumber + 1;
    sendState ← waitingForAck;
    UNTIL sendState = acked DO
      THROUGH [0..10) UNTIL sendState = acked DO
	b ← GetFreePupBuffer[];
	b.pupType ← eEnd;
	b.pupID ← [0, sendSeqNumber];
	SetPupContentsBytes[b, 0];
	sendSocket.put[b];
	WAIT send; -- 10*1 sec

	ENDLOOP;
      IF sendAbortCode # eftpOK THEN
	ERROR EFTPTroubleSending[sendAbortCode, sendErrorText];
      IF sendState = acked THEN EXIT;
      SIGNAL EFTPTimeOut;
      ENDLOOP;
    -- send the second END to keep the receiver from dallying any more
    sendSeqNumber ← sendSeqNumber + 1;
    b ← GetFreePupBuffer[];
    b.pupType ← eEnd;
    b.pupID ← [0, sendSeqNumber];
    SetPupContentsBytes[b, 0];
    sendSocket.put[b];
    IF sendAbortCode # eftpOK THEN
      ERROR EFTPTroubleSending[sendAbortCode, sendErrorText];
    END;


  -- internal procedures

  EFTPReplyForSender: PROCEDURE =
    BEGIN
    b: PupBuffer;
    UNTIL senderStop DO
      b ← sendSocket.get[];
      IF b # NIL AND (b.source = sendHim OR CheckSource[b]) THEN
	EFTPReplyForSenderLocked[b];
      IF b # NIL THEN ReturnFreePupBuffer[b];
      ENDLOOP;
    END;

  -- kludge to get around net0 troubles

  CheckSource: PROCEDURE [b: PupBuffer] RETURNS [BOOLEAN] =
    BEGIN
    network: Network ← b.network;
    IF sendHim.net = 0 THEN sendHim.net ← [network.netNumber.b];
    RETURN[b.source = sendHim];
    END;


  -- extra procedure to get lock

  EFTPReplyForSenderLocked: ENTRY PROCEDURE [b: PupBuffer] =
    BEGIN
    i, len: CARDINAL;
    SELECT b.pupType FROM
      eAck =>
	IF b.pupID = [0, sendSeqNumber] THEN
	  BEGIN sendState ← acked; NOTIFY send; END;
      eAbort =>
	BEGIN
	sendState ← acked;
	NOTIFY send;
	sendAbortCode ← LOOPHOLE[b.pupWords[0]];
	IF sendErrorText = NIL THEN
	  BEGIN
	  len ← GetPupContentsBytes[b] - 2;
	  len ← MIN[len, 100];
	  sendErrorText ← Heap.systemMDSZone.NEW[StringBody[len]];
	  FOR i IN [0..len) DO sendErrorText[i] ← b.pupChars[i + 2]; ENDLOOP;
	  sendErrorText.length ← len;
	  END;
	END;
      error =>
	IF b.errorCode = noProcessPupErrorCode THEN
	  BEGIN
	  sendState ← acked;
	  NOTIFY send;
	  sendAbortCode ← eftpRejected;
	  IF sendErrorText = NIL THEN
	    BEGIN
	    len ← GetPupContentsBytes[b] - 2*(10 + 1 + 1);
	    len ← MIN[len, 100];
	    sendErrorText ← Heap.systemMDSZone.NEW[StringBody[len]];
	    FOR i IN [0..len) DO sendErrorText[i] ← b.errorText[i]; ENDLOOP;
	    sendErrorText.length ← len;
	    END;
	  END;
      ENDCASE;
    END;

  -- Don't call this if you have the lock.  EFTPReplyForSenderLocked may need it too.

  EFTPKillSending: PROCEDURE =
    BEGIN
    senderStop ← TRUE;
    JOIN senderFork;
    IF sendSocket # NIL THEN PupSocketDestroy[sendSocket];
    sendSocket ← NIL;
    IF sendErrorText # NIL THEN
      Heap.systemMDSZone.FREE[@sendErrorText];
    END;

  -- initialization

  EFTPSetSendTimeout[1000, 25];
  END.