// PupRTPOpenClose.bcpl -- Rendezvous/Termination Protocol
// companion module is PupRTP.bcpl
// This module contains infrequently executed code which can swap
// without affecting performance.
// Copyright Xerox Corporation 1979, 1981

// last modified October 29, 1981  5:20 PM by Taft

get "Pup0.decl"
get "Pup1.decl"
get "PupRTPInternal.decl"

external
[
// outgoing procedures
OpenRTPSocket; CloseRTPSocket; CompleteRTPPup
RTPHandleUncommonPup; RTPFSM; BuildAbort

// incoming procedures
RTPSocketProcess; DefaultOtherPupProc; RTPFilter
CompletePup; AppendStringToPup; MultEq
SetPupDPort; SetPupID
InitializeContext; Block
Enqueue; Unqueue; InsertAfter
ReleasePBI; GetPBI
Zero; Allocate; Free; MoveBlock
DefaultArgs; Noop; SysErr
SetTimer; TimerHasExpired

// outgoing statics
lRTPSoc

// incoming statics
rtpStackSize; pupLevel1Ctx
pupZone; pupCtxQ; defaultTimeout
]

static [ lRTPSoc = lenRTPSoc ]

//----------------------------------------------------------------------------
let OpenRTPSocket(soc, ctxQ, openMode, connID, otherProc, timeout,
 zone; numargs na) = valof
//----------------------------------------------------------------------------
[
let defaultID = vec 1; SetTimer(defaultID, 0); defaultID!1 = soc
DefaultArgs(lv na, -1, pupCtxQ, modeInitAndWait, defaultID,
   DefaultOtherPupProc, defaultTimeout, pupZone)
let ctx = Allocate(zone, rtpStackSize)
InitializeContext(ctx, rtpStackSize, RTPSocketProcess, lenRTPCtxExtra)
ctx>>RTPCtx.socket = soc  // link from context to socket
ctx>>RTPCtx.zone = zone  // zone from which context allocated
ctx>>RTPCtx.ctxQ = ctxQ  // queue on which context placed
test ctxQ eq pupCtxQ  // same queue as PupLevel1's?
   ifso InsertAfter(ctxQ, pupLevel1Ctx, ctx)  // best position
   ifnot Enqueue(ctxQ, ctx)
soc>>RTPSoc.ctx = ctx  // link from socket to context
soc>>RTPSoc.connID↑1 = connID!0
soc>>RTPSoc.connID↑2 = connID!1
soc>>RTPSoc.rtpStatus = 0
soc>>RTPSoc.rtpOtherPupProc = otherProc
soc>>RTPSoc.rtpOtherTimerProc = Noop
let initState = nil
switchon openMode into
   [
   case modeInitAndWait: case modeInitAndReturn:
      [
      initState = RFCO
      RTPFSM(soc, OPNC)
      if openMode eq modeInitAndReturn then resultis true
      endcase
      ]
   case modeListenAndWait: case modeListenAndReturn:
      [
      initState = LIST; soc>>RTPSoc.wasListening = true
      RTPFSM(soc, OPNL)
      if openMode eq modeListenAndReturn then resultis true
      endcase
      ]
   case modeImmediateOpen:
      [
      RTPFSM(soc, OPNN)
      resultis true
      ]
   ]
let openTimer, state = nil, nil; SetTimer(lv openTimer, timeout)
   [
   Block()
   state = soc>>RTPSoc.state
   ] repeatuntil state ne initState % TimerHasExpired(lv openTimer)
if state eq OPEN % state eq ENDI then resultis true
CloseRTPSocket(soc, 0); resultis false
]

//----------------------------------------------------------------------------
and CloseRTPSocket(soc, timeout; numargs na) = valof
//----------------------------------------------------------------------------
// locally initiated close
[
if na ls 2 then timeout = defaultTimeout
if timeout ne 0 then RTPFSM(soc, CLSN)
let timer, state = nil, nil; SetTimer(lv timer, timeout)
   [
   state = soc>>RTPSoc.state
   test state eq ABOR % TimerHasExpired(lv timer)
      ifso [ RTPFSM(soc, CLST); timeout = 0 ]
      ifnot Block()
   ] repeatuntil state eq CLOS
let ctx = soc>>RTPSoc.ctx
while ctx>>RTPCtx.unclean do Block()
Unqueue(ctx>>RTPCtx.ctxQ, ctx)
Free(ctx>>RTPCtx.zone, ctx)
resultis timeout ne 0
]

//----------------------------------------------------------------------------
and RTPHandleUncommonPup(soc, pbi) = valof
//----------------------------------------------------------------------------
// Called for all Pup known to RTP, namely RFC, End, EndReply, Abort, Error.
// Either releases the supplied PBI and returns true, or leaves it to be
// passed to the higher-level handler and returns false.
[
let event = nil
switchon pbi>>PBI.pup.type into
   [
   case typeRFC:
      if pbi>>PBI.pup.bytes↑1 eq 0 then  // default conn port net
         pbi>>PBI.pup.bytes↑1 = pbi>>PBI.pup.sPort.net
      test selecton soc>>RTPSoc.state into
         [
         case LIST: RTPFilter(pbi, false, false)
         case RFCO: RTPFilter(pbi, true, true)
         case OPEN:
         case ENDO: RTPFilter(pbi, false, true) &
          MultEq(lv pbi>>PBI.pup.words, lv soc>>RTPSoc.frnPort, 3)
         default: false
         ]
         ifso RTPFSM(soc, RRFC, pbi)
         ifnot
            [
            let npbi = BuildAbort(soc, 0, "RFC refused")
            SetPupID(npbi, lv pbi>>PBI.pup.id)
            SetPupDPort(npbi, lv pbi>>PBI.pup.sPort)
            CompletePup(npbi)
            ]
      endcase

   case typeEnd:
      event = REND; docase -1

   case typeEndReply:
      event = RENR; docase -1

   case typeAbort:
      event = RABT; // docase -1

   case -1: // End, EndReply, Abort common handling
      if RTPFilter(pbi, true, true) then
         [
         RTPFSM(soc, event, pbi)
         unless event eq RENR do
            resultis false  // Ends and Aborts passed on
         ]
      endcase

   case typeError:
      if pbi>>PBI.pup.words↑11 eq 2 & RTPFilter(pbi, true, false) then
         RTPFSM(soc, RABT, pbi)  // treat type 2 same as Abort
      resultis false  // Errors passed on
   ]

// if we get here, dispose of the PBI locally
ReleasePBI(pbi)
resultis true  // I did it
]

//----------------------------------------------------------------------------
and RTPFSM(soc, event, pbi) be
//----------------------------------------------------------------------------
// procedure to run the RTP finite state machine
[RTPFSM

structure Entry: [ action byte; nextState byte ]
structure FSM↑CLOS,ABOR↑OPNC,TIMO: @Entry

let fsm = table  // state x event => action+nextstate
[
// current state:
// CLOS    RFCO       LIST       OPEN       ENDI       ENDO       DALY       ABOR      //event

SRF1+RFCO; LERR+RFCO; LERR+LIST; LERR+OPEN; LERR+ENDI; LERR+ENDO; LERR+DALY; LERR+ABOR //OPNC
NOOP+LIST; LERR+RFCO; LERR+LIST; LERR+OPEN; LERR+ENDI; LERR+ENDO; LERR+DALY; LERR+ABOR //OPNL
NOOP+OPEN; LERR+RFCO; LERR+LIST; LERR+OPEN; LERR+ENDI; LERR+ENDO; LERR+DALY; LERR+ABOR //OPNN
NOOP+CLOS; SABT+CLOS; NOOP+CLOS; SEND+ENDO; SENR+DALY; NOOP+ENDO; NOOP+DALY; NOOP+CLOS //CLSN
NOOP+CLOS; SABT+CLOS; NOOP+CLOS; SABT+CLOS; SABT+CLOS; SABT+CLOS; NOOP+CLOS; NOOP+CLOS //CLST
FERR+CLOS; OPNX+OPEN; SRF2+OPEN; SRF2+OPEN; FERR+ENDI; SRF2+ENDO; FERR+DALY; FERR+ABOR //RRFC
FERR+CLOS; NOOP+ABOR; FERR+LIST; NOOP+ABOR; NOOP+ABOR; NOOP+ABOR; NOOP+ABOR; NOOP+ABOR //RABT
FERR+CLOS; FERR+RFCO; FERR+LIST; NOOP+ENDI; NOOP+ENDI; SENR+DALY; SENR+DALY; FERR+ABOR //REND
FERR+CLOS; FERR+RFCO; FERR+LIST; FERR+OPEN; FERR+ENDI; SENR+CLOS; NOOP+CLOS; FERR+ABOR //RENR
NOOP+CLOS; SRF1+RFCO; NOOP+LIST; NOOP+OPEN; NOOP+ENDI; SEND+ENDO; NOOP+CLOS; NOOP+ABOR //TIMO
]

let timeout = table  // timeout for state being entered
[
32000;     100;       32000;     32000;     32000;     100;       1000;      32000
]

let entry = fsm>>FSM↑event↑(soc>>RTPSoc.state)
RTPAction(entry<<Entry.action, soc, pbi)
soc>>RTPSoc.state = entry<<Entry.nextState
SetTimer(lv soc>>RTPSoc.rtpTimer, timeout!(soc>>RTPSoc.state))
]RTPFSM

//----------------------------------------------------------------------------
and RTPAction(action, soc, pbi) be
//----------------------------------------------------------------------------
// Actions invoked by the fsm
[
switchon action into
   [
   case SRF1 rshift 8:  // send initiating rfc
      pbi = GetPBI(soc)
      MoveBlock(lv pbi>>PBI.pup.words, lv soc>>RTPSoc.lclPort, lenPort)
      CompleteRTPPup(pbi, typeRFC, pupOvBytes+2*lenPort)
      endcase

   case SRF2 rshift 8:  // send answering rfc
      if soc>>RTPSoc.wasListening then
         [
         if soc>>RTPSoc.state eq LIST then
            [
            MoveBlock(lv soc>>RTPSoc.connID, lv pbi>>PBI.pup.id, 2)
            MoveBlock(lv soc>>RTPSoc.frnPort, lv pbi>>PBI.pup.words, lenPort)
            MoveBlock(lv soc>>RTPSoc.lclPort, lv pbi>>PBI.pup.dPort, lenPort)
            ]
         let npbi = GetPBI(soc)
         SetPupDPort(npbi, lv pbi>>PBI.pup.sPort)
         MoveBlock(lv npbi>>PBI.pup.words, lv soc>>RTPSoc.lclPort, lenPort)
         CompleteRTPPup(npbi, typeRFC, pupOvBytes+2*lenPort)
         ]
      endcase

   case OPNX rshift 8:  // got answer back to our rfc
      MoveBlock(lv soc>>RTPSoc.frnPort, lv pbi>>PBI.pup.words, lenPort)
      endcase

   case SABT rshift 8:  // send abort
      pbi = BuildAbort(soc, 0, (soc>>RTPSoc.state eq RFCO?
         "Connection attempt aborted", "Connection aborted"))
      SetPupID(pbi, lv soc>>RTPSoc.connID)
      CompletePup(pbi)
      endcase

   case SEND rshift 8:  // send end
   case SENR rshift 8:  // send end reply
      pbi = GetPBI(soc)
      // do this in-line to conserve stack space
      SetPupID(pbi, lv pbi>>PBI.socket>>RTPSoc.connID)
      CompletePup(pbi, (action eq SEND rshift 8? typeEnd, typeEndReply),
       pupOvBytes)
      endcase

   case LERR rshift 8:  // local error (my bug)
      SysErr(soc, ecImproperStateForEvent)

   case FERR rshift 8:  // foreign error (his bug)
   ]
]

//----------------------------------------------------------------------------
and BuildAbort(soc, code, string) = valof
//----------------------------------------------------------------------------
[
let pbi = GetPBI(soc)
pbi>>PBI.pup.type = typeAbort
pbi>>PBI.pup.words↑1 = code
AppendStringToPup(pbi, 3, string)
resultis pbi
]

//----------------------------------------------------------------------------
and CompleteRTPPup(pbi, type, length) be
//----------------------------------------------------------------------------
[
SetPupID(pbi, lv pbi>>PBI.socket>>RTPSoc.connID)
CompletePup(pbi, type, length)
]