-- File: EFTPRecv.mesa,  Last Edit:
  -- MAS April 17, 1980  8:51 PM
  -- HGM October 13, 1980  8:01 PM

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  InlineDefs: FROM "InlineDefs" USING [COPY],
  CommUtilDefs: FROM "CommUtilDefs" USING [SetTimeout, MsecToTicks],
  PupDefs: FROM "PupDefs" USING [
    DataWordsPerPupBuffer, GetFreePupBuffer, ReturnFreePupBuffer,
    GetPupContentsBytes, SetPupContentsBytes,
    PupAddress, PupBuffer,
    PupRouterSendThis, PupSocket, PupSocketMake, PupSocketDestroy,
    SecondsToTocks, SwapPupSourceAndDest],
  PupTypes:  FROM "PupTypes" USING [
    maxDataBytesPerGatewayPup, fillInPupAddress],
  EFTPDefs:  FROM "EFTPDefs" USING [
    EFTPTimeOut, EFTPAbortCode, eftpExternalReceiverAbort,
    eftpOutOfSyncAbort, eftpReceiverBusyAbort];

EFTPRecv: MONITOR
  IMPORTS InlineDefs, CommUtilDefs, EFTPDefs, PupDefs
  EXPORTS EFTPDefs =
BEGIN OPEN PupDefs, EFTPDefs;
  
EFTPAlreadyReceiving: PUBLIC ERROR = CODE;
EFTPNotReceiving: PUBLIC ERROR = CODE;
EFTPEndReceiving: PUBLIC ERROR = CODE;
EFTPTroubleReceiving: PUBLIC ERROR [e: EFTPAbortCode, s: STRING] = CODE;

recv: CONDITION;
recverStop: BOOLEAN;
recverFork: PROCESS;
receiveSocket: PupSocket ← NIL;
recvStarted: BOOLEAN;
recvHim: PupAddress;
receiveSeqNumber: CARDINAL;
receivePupBuffer: PupBuffer ← NIL;
receiveGobbled: CARDINAL ← 0;  -- in words !



EFTPSetRecvTimeout: PUBLIC PROCEDURE [ms: CARDINAL] =
  BEGIN
  CommUtilDefs.SetTimeout[@recv,CommUtilDefs.MsecToTicks[ms]];
  END;

EFTPOpenForReceiving: PUBLIC PROCEDURE [me: PupAddress] RETURNS [PupAddress] =
  BEGIN
  EFTPOpenCheck[me];
  BEGIN ENABLE UNWIND => EFTPKillReceiver[];
  EFTPOpenLocked[];
  END;
  RETURN[recvHim];
  END;

EFTPOpenCheck: ENTRY PROCEDURE [me: PupAddress] =
  BEGIN ENABLE UNWIND => NULL;
  IF receiveSocket#NIL THEN ERROR EFTPAlreadyReceiving;
  recvStarted ← FALSE;
  recverStop ← FALSE;
  recverFork ← FORK EFTPRecvDataProcess[];
  receiveSocket ← PupSocketMake[
    me.socket,
    PupTypes.fillInPupAddress,
    SecondsToTocks[1]];
  receiveSeqNumber ← 0;
  END;

EFTPOpenLocked: ENTRY PROCEDURE =
  BEGIN ENABLE UNWIND => NULL;
  UNTIL receivePupBuffer#NIL DO    
    THROUGH [0..10) UNTIL receivePupBuffer#NIL DO WAIT recv; ENDLOOP;
    IF receivePupBuffer#NIL THEN EXIT;
    SIGNAL EFTPTimeOut;  -- 10*1 sec
    ENDLOOP;
  receiveSocket.setRemoteAddress[recvHim];
  END;

EFTPGetBlock: PUBLIC ENTRY PROCEDURE [p: POINTER, l: CARDINAL] RETURNS [CARDINAL] =
  BEGIN ENABLE UNWIND => NULL;
  n: CARDINAL;  -- bytes to transfer
  pupLength: CARDINAL;
  IF receiveSocket=NIL THEN ERROR EFTPNotReceiving;
  IF (l MOD 2)=1 THEN l ← l-1;
  UNTIL receivePupBuffer#NIL DO
    WAIT recv;
    IF receivePupBuffer#NIL THEN EXIT;
    SIGNAL EFTPTimeOut;  -- 1*1 sec
    ENDLOOP;
  IF receivePupBuffer.pupType=eEnd OR receivePupBuffer.pupType=eAbort THEN
    BEGIN
    b: PupBuffer ← receivePupBuffer;
    receivePupBuffer ← NIL;
    EFTPSendAck[b,receiveSeqNumber];
    RETURN WITH ERROR EFTPEndReceiving;
    END;
  pupLength ← GetPupContentsBytes[receivePupBuffer];
  n ← pupLength-(receiveGobbled*2);
  n ← MIN[l,n];
  InlineDefs.COPY[
    from: @receivePupBuffer.pupWords[receiveGobbled],
    nwords: (n+1)/2,
    to: p];
  receiveGobbled ← receiveGobbled+(n/2);
  IF l>n OR receiveGobbled*2=pupLength THEN
    BEGIN  -- done with this buffer, use it to send back the ack
    b: PupBuffer ← receivePupBuffer;
    receivePupBuffer ← NIL;
    EFTPSendAck[b,receiveSeqNumber];
    receiveSeqNumber ← receiveSeqNumber+1;
    END;
  RETURN[n];
  END;

EFTPFinishReceiving: PUBLIC PROCEDURE =
  BEGIN
  IF receiveSocket=NIL THEN ERROR EFTPNotReceiving;
  EFTPKillReceiver[];
  END;

EFTPAbortReceiving: PUBLIC PROCEDURE [s: STRING] =
  BEGIN
  IF receiveSocket=NIL THEN RETURN;
  EFTPAbortReceivingLocked[s];
  EFTPKillReceiver[];
  END;

EFTPAbortReceivingLocked: ENTRY PROCEDURE [s: STRING] =
  BEGIN
  i, end: CARDINAL;
  b: PupBuffer;
  b ← GetFreePupBuffer[];
  b.pupType ← eAbort;
  b.pupID ← [0,receiveSeqNumber];
  b.pupWords[0] ← eftpExternalReceiverAbort;
  end ← MIN[
    s.length+2,
    2*DataWordsPerPupBuffer[],
    PupTypes.maxDataBytesPerGatewayPup];
  FOR i ← 2,i+1 UNTIL i=end DO
    b.pupChars[i] ← s[i-2];
    ENDLOOP;
  SetPupContentsBytes[b,end];
  receiveSocket.put[b];
  END;

-- internal procedures
EFTPRecvDataProcess: PROCEDURE =
  BEGIN
  b: PupBuffer;
  UNTIL recverStop DO
    b ← receiveSocket.get[];
    IF b#NIL THEN EFTPRecvDataLocked[b];
    ENDLOOP;
  END;

EFTPRecvDataLocked: ENTRY PROCEDURE [b: PupBuffer] =
  BEGIN -- better not SIGNAL here
  IF b.pupType ~IN[eData..eAbort] THEN  -- not EFTP type packet
    BEGIN
    ReturnFreePupBuffer[b];
    GOTO AllDone;
    END;
  IF b.source=recvHim AND b.pupType=eData
  AND b.pupID=[0,receiveSeqNumber-1] THEN
    BEGIN  -- duplicate: use the buffer to send back the ack
    EFTPSendAck[b,receiveSeqNumber-1];
    GOTO AllDone;
    END;
  IF b.source#recvHim OR b.pupID#[0,receiveSeqNumber] THEN
    BEGIN  -- from wrong place
    IF ~recvStarted AND b.pupID=[0,0] THEN
      BEGIN  -- we are listening, and this is the first packet
      IF receivePupBuffer=NIL THEN  -- take this new user
        BEGIN
        receivePupBuffer ← b;
        receiveGobbled ← 0;
        recvStarted ← TRUE;
        recvHim ← receivePupBuffer.source;
        NOTIFY recv;
        GOTO AllDone;
        END;
      END;
    IF b.pupType=eData THEN
      BEGIN  -- use the buffer to send back an abort
      b.pupType ← eAbort;
      b.pupWords[0] ← IF b.pupID#[0,0] THEN eftpOutOfSyncAbort ELSE eftpReceiverBusyAbort;
      SetPupContentsBytes[b,2];  -- should send some text
      SwapPupSourceAndDest[b];
      PupRouterSendThis[b];
      GOTO AllDone;
      END;
    ReturnFreePupBuffer[b];
    GOTO AllDone;
    END;
  -- must be the one we want
  IF receivePupBuffer#NIL THEN
    BEGIN -- Oops, retransmission.  We will ack when we finish reading this buffer.
    ReturnFreePupBuffer[b];
    GOTO AllDone;
    END;
  receivePupBuffer ← b;
  receiveGobbled ← 0;
  NOTIFY recv;
  EXITS AllDone => NULL;
  END;

EFTPSendAck: PROCEDURE [b: PupBuffer, id: CARDINAL] =
  BEGIN
  b.pupType ← eAck;
  b.pupID ← [0,id];
  SetPupContentsBytes[b,0];
  receiveSocket.put[b];
  END;

EFTPKillReceiver: PROCEDURE =
  BEGIN
  recverStop ← TRUE;
  JOIN recverFork;
  IF receiveSocket#NIL THEN PupSocketDestroy[receiveSocket];
  receiveSocket ← NIL;
  IF receivePupBuffer#NIL THEN
    BEGIN
    b: PupBuffer ← receivePupBuffer;
    receivePupBuffer ← NIL;
    ReturnFreePupBuffer[b];
    END;
  END;

-- initialization
EFTPSetRecvTimeout[1000];
END.