// PupEFTPReceive.bcpl -- by J. Shoch
// Copyright Xerox Corporation 1979

// Last modified June 5, 1979  12:12 PM by Taft

get "PupEFTP.decl"

external
[
// outgoing procedures
ReceiveEFTPBlock; ReceiveEFTPPacket

// incoming procedures
SendEFTPAbort
GetPBI; ReleasePBI; CompletePup
SetTimer; TimerHasExpired; Block; Dequeue; MultEq
MoveBlock
]


//----------------------------------------------------------------------------
let ReceiveEFTPBlock(soc, addr, timeout) = valof
//----------------------------------------------------------------------------
// Returns a byte count (>=0) or an error code (<0)
[
let inPbi = 0
let receiveResult = ReceiveEFTPPacket(soc, timeout, lv inPbi)
if receiveResult ge 0 then // succeeded
   [
   MoveBlock(addr, lv inPbi>>PBI.pup.words↑1, (receiveResult+1) rshift 1)
   if inPbi then  // ignore zero-length packets
      [ ReleasePBI(inPbi); if receiveResult eq 0 loop ]
   resultis receiveResult // count of dataBytes
   ]
unless receiveResult eq EFTPNotFirstSynch resultis receiveResult
] repeat

//----------------------------------------------------------------------------
and ReceiveEFTPPacket(soc, timeout, lvPbi) = valof
//----------------------------------------------------------------------------
// Returns a byte count (>=0) or an error code (<0)
// If a byte count, @lvPbi contains the pbi with the data
// Note that when we start, we may not yet know the socket
//  number of the foreign socket.....
[
let syncMsg = "Your sender has gotten out of sync."
let busyMsg = "Receiver busy, please try again later."
let longTimer = nil; SetTimer(lv longTimer, timeout)
   [ // repeat
   // wait for an incoming packet
   Block()
   if timeout ne -1 & TimerHasExpired(lv longTimer) resultis EFTPTimeout
   let inPbi = Dequeue(lv soc>>EFTPSoc.iQ)
   if inPbi eq 0 loop
   let inSeqNum = inPbi>>PBI.pup.id↑2

   // See if we are already committed to a file transfer
   if soc>>EFTPSoc.TransferNotStarted then
      [
      // No established file transfer is in progress.
      // To start a file transfer, (1) his must be a valid packet 0,
      //  and either (2) its source must match our socket's foreign port
      //  or (3) we must be promiscuous.
      if inPbi>>PBI.pup.type eq typeEFTPData & inSeqNum eq 0 &
       (soc>>EFTPSoc.frnPort.host eq 0 % // (2)
       (soc>>EFTPSoc.frnPort.host eq inPbi>>PBI.pup.sPort.host & // (3)
       soc>>EFTPSoc.frnPort.net eq inPbi>>PBI.pup.sPort.net)) then // (3)
         [ // establish a "connection"
         soc>>EFTPSoc.TransferNotStarted = false
         soc>>EFTPSoc.SeqNum = 0
         MoveBlock(lv soc>>EFTPSoc.frnPort, lv inPbi>>PBI.pup.sPort, lenPort)
         ]
      ]

   // Do source filtering
   unless MultEq(lv soc>>EFTPSoc.frnPort, lv inPbi>>PBI.pup.sPort, lenPort) do
      [ // Did not pass source filter, generate Abort
      test inPbi>>PBI.pup.type eq typeEFTPData & inSeqNum eq 0
         ifso
            [
            // Attempted first packet of transfer.
            // Either we are busy or we are listening and not promiscuous.
            SendEFTPAbort(soc, ReceiverBusyAbort, busyMsg, inPbi)
            soc>>EFTPSoc.SomeoneElseWaiting = true
            ]
         ifnot
            [
            // Not legal first packet of transfer.
            SendEFTPAbort(soc, OutOfSynchAbort, syncMsg, inPbi)
            ]
      ReleasePBI(inPbi)
      if soc>>EFTPSoc.TransferNotStarted resultis EFTPNotFirstSynch
      loop   //wait for next input
      ]

   // Check the sequence number -- should be expected or expected-1
   if (soc>>EFTPSoc.SeqNum-inSeqNum) rshift 1 ne 0 then 
      [
      // Sequence number incorrect.
      // If we receive a data packet with sequence number 0
      // from our partner, then assume he wants to restart.
      // Otherwise abort the transfer and return an error.
      unless inSeqNum eq 0 do
         SendEFTPAbort(soc, OutOfSynchAbort, syncMsg, inPbi)
      ReleasePBI(inPbi)
      resultis inSeqNum eq 0? EFTPResetReceived, EFTPAbortSent
      ]

// ReceiveEFTPPacket (cont'd)

   // Packet has passed all filters.  Now process it.
   switchon inPbi>>PBI.pup.type into
      [
      case typeEFTPData:
         [
         AckEFTPPacket(soc, inPbi)
         if inSeqNum ne soc>>EFTPSoc.SeqNum then
            [ ReleasePBI(inPbi); loop ]  //retransmission
         soc>>EFTPSoc.SeqNum = soc>>EFTPSoc.SeqNum+1
         @lvPbi = inPbi
         resultis inPbi>>PBI.pup.length - pupOvBytes   //and return
         ]
      case typeEFTPEnd:
         [
         AckEFTPPacket(soc, inPbi); ReleasePBI(inPbi)
         if inSeqNum ne soc>>EFTPSoc.SeqNum loop  //retransmission
         soc>>EFTPSoc.SeqNum = soc>>EFTPSoc.SeqNum+1
         let dallyTimer = nil; SetTimer(lv dallyTimer, dallyTimeout)
            [ // repeat
            Block()
            if TimerHasExpired(lv dallyTimer) resultis EFTPEndReceived
            inPbi = Dequeue(lv soc>>EFTPSoc.iQ)
            if inPbi eq 0 loop

            if inPbi>>PBI.pup.type ne typeEFTPEnd then
               [ ReleasePBI(inPbi); loop ]
            if soc>>EFTPSoc.SeqNum eq inPbi>>PBI.pup.id↑2 then   
               [ ReleasePBI(inPbi); resultis EFTPEndReceived ]

            // Was a retransmission of first end, ack it
            AckEFTPPacket(soc, inPbi); ReleasePBI(inPbi)
            ] repeat
         endcase
         ]
      case typeEFTPAbort:
         [
         if soc>>EFTPSoc.AbortPBI ne 0 then ReleasePBI(soc>>EFTPSoc.AbortPBI)
         soc>>EFTPSoc.AbortPBI = inPbi
         resultis EFTPAbortReceived
         ]
      ]
   ReleasePBI(inPbi)
   ] repeat
]
   

//----------------------------------------------------------------------------
and AckEFTPPacket(soc, pbi) be
//----------------------------------------------------------------------------
[
// construct the outgoing ack packet
let ackPbi = GetPBI(soc)
ackPbi>>PBI.pup.id↑2 = pbi>>PBI.pup.id↑2
CompletePup(ackPbi, typeEFTPAck, pupOvBytes)
]