// IfsSequinSwap.bcpl - Sequin - SWAPPABLE
// Copyright Xerox Corporation 1979, 1980, 1981, 1982

// Last modified by September 5, 1982  12:32 PM by Taft

get ecOutOfSequence, ecVPBIBackChain, ecSequinError from "IfsLeafErrors.decl";
get "IfsLeaf.decl";
get "IfsSequin.decl";

external
[
//outgoing procedures
HandleLeafRejection; SequenceCompare; SequinCtx;
SequinBreak; SequinDestroy; SequinReset;

//incoming procedures
SysAllocate; SysAllocateZero; SysFree; Block; CompletePup;
Dequeue; DoubleIncrement; Enqueue; ExchangePorts;
FindLockedSequin; IFSError; LeafFinish; LockSequin; MoveBlock; ReadVPBI;
ReleaseVPBI; ReleasePBI; SequinInput; SequinKickReaper; SequinStartOutput;
SetTimer; TimerHasExpired; UnlockSequin; Zero;

//incoming statics
entFlag; haltFlag; leafEnabled; lenPup
sequinAnomalies; scb; socLeaf;
]

static sequinDebug = false;

//----------------------------------------------------------------------------
let SequinCtx() be
//----------------------------------------------------------------------------
[
let timer = nil; let noActivity = nil;
   [ // repeat
      [
      let pbi = Dequeue(lv socLeaf>>PupSoc.iQ); if pbi eq 0 break;
      // Allow for retransmit activity
      SetTimer(lv timer, activityTimeout); noActivity = false;
      if pbi>>PBI.pup.type ne typeLeaf then [ ReleasePBI(pbi); loop; ]
      let sequin = FindLockedSequin(lv pbi>>PBI.pup.sPort);
      if sequin eq 0 then sequin = OpenSequin(pbi); if sequin eq 0 loop;
      HandlePBI(pbi, sequin); UnlockSequin(sequin);
      ] repeat
   Block();
   noActivity = noActivity % TimerHasExpired(lv timer);
   ] repeatuntil scb>>SCB.sequinQ.head eq 0 & noActivity
LeafFinish();    // Doesn't return;
]

//----------------------------------------------------------------------------
and OpenSequin(pbi) = valof
//----------------------------------------------------------------------------
[
if (lv pbi>>PBI.pup.id)>>SequinID.control ne sequinOpen then
   [ SequinAnswer(0, pbi, sequinBroken); resultis 0; ]
if haltFlag % not (leafEnabled & entFlag) then
   [ LeafRFCFail(pbi); resultis 0; ]

// Note that openState = 0, sequinData = 0...initial state.
let sequin = SysAllocateZero(lenSequin);
MoveBlock(lv sequin>>Sequin.port, lv pbi>>PBI.pup.sPort, lenPort);
sequin>>Sequin.pupDataBytes = 2*lenPup - pupOvBytes;
sequin>>Sequin.lockTimeout = defaultLockTimeout;
sequin>>Sequin.connTimeout = defaultConnTimeout;
sequin>>Sequin.allocate = maxInputVPBIs;
sequin>>Sequin.inputLink = -1; sequin>>Sequin.outputLink = -1;
Enqueue(lv scb>>SCB.sequinQ, sequin); LockSequin(sequin);
DoubleIncrement(lv scb>>SCB.acceptedRFCs);
resultis sequin;
]

//----------------------------------------------------------------------------
and HandlePBI(pbi, sequin) be
//----------------------------------------------------------------------------
[
let pbiID = lv pbi>>PBI.pup.id;
let control = pbiID>>SequinID.control;
// Open, close are also sequenced data packets.
if control eq sequinOpen % control eq sequinClose then control = sequinData;

// check pbi's send sequence
switchon SequenceCompare(pbiID>>SequinID.sendSequence,
 sequin>>Sequin.receiveSequence) into
   [
   case outOfRange:
      SequinError(sequin, pbi); return;
   case ahead:
      SequinAnswer(sequin, pbi, sequinRestart); return;
   case duplicate:
      [
      if SequenceCompare(pbiID>>SequinID.sendSequence + 1,  // the latest?
       sequin>>Sequin.receiveSequence) ne equal % control ne sequinData then
        [ ReleasePBI(pbi); return; ]  // no, ignore it
      control = sequinRestart;  // yes, make it a restart
      ] 
   ]

// equal sequence..(or fake restart)
//copy the allocate (N.B. not ordered between data and controlData messages)
sequin>>Sequin.allocated = pbiID>>SequinID.allocate

// handle any acks
let ackedSequence = sequin>>Sequin.ackedState.current;
switchon SequenceCompare(pbiID>>SequinID.receiveSequence, ackedSequence) into
   [ // Careful!! Sequence numbers are 8 bits in the pbi, 16 in the sequin.
   case ahead:
      [
      let newAckedSeq = ackedSequence +
       ((pbiID>>SequinID.receiveSequence - ackedSequence) & #377);
      if (newAckedSeq - sequin>>Sequin.sendSequence) gr 0 then docase outOfRange;
      sequin>>Sequin.ackedState.current = newAckedSeq;
      SequinKickReaper(sequin);
      unless sequin>>Sequin.retransmitting do SequinHandleAcks(sequin);
      endcase;
      ]
   case outOfRange:
      SequinError(sequin, pbi); return;
   case duplicate:
      ReleasePBI(pbi); return;
   ]

// act on state conditions
switchon sequin>>Sequin.state into
   [
   case closedState:
      // check original control op in packet for transition to openState.
      if pbiID>>SequinID.control ne sequinData then docase openState;
   case timedOutState:
      sequin>>Sequin.control = sequinData;
      sequin>>Sequin.state = openState;
   case openState:
      sequin>>Sequin.timerCount = sequin>>Sequin.lockTimeout;  // Reset timer
      endcase;
   case brokenState:
      SequinAnswer(sequin, pbi, sequinBroken); return;
   case dallyingState:
      unless control eq sequinQuit do
         [ SequinAnswer(sequin, pbi, sequinDallying); return; ]
      SequinDestroy(sequin);
   case destroyedState:
      ReleasePBI(pbi); return;
   ]

// dispatch on control field
// guaranteed either open or closed state here
switchon control into
   [
   case sequinDestroy:
      [
      sequin>>Sequin.state = dallyingState;
      SequinAnswer(sequin, pbi, sequinDallying); endcase;
      ]
   case sequinCheck:
   case sequinNop:
      SequinAnswer(sequin, pbi, sequinAck); endcase;

   case sequinRestart:
      [
      if sequin>>Sequin.sendSequence ne sequin>>Sequin.ackedState.current do
         [
         sequin>>Sequin.retransmitting = true;
         SequinStartOutput(sequin);
         ]
      // fall through
      ]
   case sequinAck:
      ReleasePBI(pbi); endcase;
   case sequinData:
      [
      if sequin>>Sequin.state ne openState then docase sequinStateError;
      if pbiID>>SequinID.control eq sequinClose then
         [
         sequin>>Sequin.control = sequinClosed;
         sequin>>Sequin.state = closedState;
         ]
      sequin>>Sequin.haltTimerCount = haltTimeout;
      if pbi>>PBI.pup.length eq pupOvBytes then
         [ SequinAnswer(sequin, pbi); endcase; ]
      SequinInput(sequin, pbi); ReleasePBI(pbi);
      endcase;
      ]
   case sequinBreak:
   case sequinRetransmit:  // requires keeping two retransmitQueue's
   case sequinStateError:
   default: SequinError(sequin, pbi);
   ]
]

//----------------------------------------------------------------------------
and SequenceCompare(this, that) = valof
//----------------------------------------------------------------------------
[
let diff = (this - that) & #377
if diff eq 0 resultis equal
if diff le maxGetAheadVPBIs resultis ahead
if diff ge #377 - maxGetAheadVPBIs resultis duplicate
resultis outOfRange
]

//----------------------------------------------------------------------------
and SequinHandleAcks(sequin) be
//----------------------------------------------------------------------------
[
let sequence = sequin>>Sequin.sendSequence;
let ackedSequence = sequin>>Sequin.ackedState.current;
let vecLength = ackedSequence - sequin>>Sequin.ackedState.reaped;
if vecLength eq 0 return;
let id = sequin>>Sequin.lastSendVPBIID;
let ackedVec = SysAllocate(vecLength);
let vecIndex = 0;
   [ // Scan the backward chain here.
   sequence = sequence - 1;
   if ackedSequence - sequence gr 0 then
      [
      if vecIndex ge vecLength then IFSError(ecVPBIBackChain);   
      ackedVec!vecIndex = id; vecIndex = vecIndex + 1;
      ]
   let diff = sequence - sequin>>Sequin.ackedState.reaped;
   if diff eq 0 break;
   if diff ls 0 then [ vecIndex = vecIndex + diff; break; ]
   let vpbi = ReadVPBI(id, cleanPage + inCoreOp, true);
   if vpbi eq 0 then [ SysFree(ackedVec); return; ]
   if sequence ne vpbi>>VPBI.sequence then IFSError(ecOutOfSequence);
   id = vpbi>>VPBI.backChain;
   ] repeat

// Release the vpbis here!
until vecIndex eq 0 do
   [ 
   vecIndex = vecIndex - 1;
   let vpbi = ReadVPBI(ackedVec!vecIndex, dirtyPage + inCoreOp, true);
   if vpbi eq 0 break; ReleaseVPBI(vpbi);
   ]
SysFree(ackedVec);
]

//----------------------------------------------------------------------------
and SequinAnswer(sequin, pbi, control; numargs na) be
//----------------------------------------------------------------------------
[
// N.B. This is the only place where CompletePup can be called with a real PBI
// This should never be called with na ls 3 and sequin = 0;
let pbiID = lv pbi>>PBI.pup.id;
if sequin ne 0 then
   [
   pbiID>>SequinID.allocate =
    sequin>>Sequin.allocate - sequin>>Sequin.freeInputSlot;
   pbiID>>SequinID.receiveSequence = sequin>>Sequin.receiveSequence
   pbiID>>SequinID.sendSequence = sequin>>Sequin.sendSequence
   if na ls 3 then control = sequin>>Sequin.control;
   ]
pbiID>>SequinID.control = control;
ExchangePorts(pbi); CompletePup(pbi, typeLeaf, pupOvBytes);
]

//----------------------------------------------------------------------------
and HandleLeafRejection() be
//----------------------------------------------------------------------------
[
let pbi = Dequeue(lv socLeaf>>PupSoc.iQ); if pbi eq 0 return;
test pbi>>PBI.pup.type eq typeLeaf &
 (lv pbi>>PBI.pup.id)>>SequinID.control eq sequinOpen
   ifso LeafRFCFail(pbi)
   ifnot ReleasePBI(pbi);
] repeat

//----------------------------------------------------------------------------
and LeafRFCFail(pbi) be
//----------------------------------------------------------------------------
[
DoubleIncrement(lv scb>>SCB.refusedRFCs);
SequinAnswer(0, pbi, sequinBroken);  // *** More info to user later.
]

//----------------------------------------------------------------------------
and SequinBreak(sequin) be
//----------------------------------------------------------------------------
[
sequin>>Sequin.state = brokenState;
sequin>>Sequin.control = sequinBroken;
]

//----------------------------------------------------------------------------
and SequinDestroy(sequin) be
//----------------------------------------------------------------------------
[
sequin>>Sequin.state = destroyedState;
SequinKickReaper(sequin);
]

//----------------------------------------------------------------------------
and SequinError(sequin, pbi) be
//----------------------------------------------------------------------------
[
if sequinDebug then IFSError(ecSequinError);
SequinBreak(sequin); SequinAnswer(sequin, pbi);
]

//----------------------------------------------------------------------------
and SequinReset(sequin) be
//----------------------------------------------------------------------------
[
test sequin>>Sequin.fhQ.head eq 0
   ifso SequinDestroy(sequin);
   ifnot
      [
      sequin>>Sequin.state = timedOutState;
      Zero(lv sequin>>Sequin.port.socket, 2)
      ]
]