// Pup1OpenClose.bcpl -- Pup level 1
// This module contains infrequently executed code which can be swapped
// without affecting performance.
// Copyright Xerox Corporation 1979, 1981

// Last modified November 21, 1981  10:58 AM by Taft

get "pup0.decl"
get "pup1.decl"

external
[
// outgoing procedures
OpenLevel1Socket; CloseLevel1Socket; FlushQueue
SetAllocation; AppendStringToPup

// incoming procedures
CompletePup; ReleasePBI
Zero; MoveBlock; SysErr; DefaultArgs
Enqueue; Dequeue; Unqueue; Block
HLookup

// outgoing statics
lPupSoc; socketSequence

// incoming statics
ndbQ; socketQ; dPSIB; pupRT; numNets
]

static
[
lPupSoc = lenPupSoc  // so client doesn't need to get Pup1.decl
socketSequence  // for making unique local socket numbers
]

//----------------------------------------------------------------------------
let OpenLevel1Socket(soc, lclPort, frnPort, transient; numargs na) = valof
//----------------------------------------------------------------------------
// soc is a pointer to storage of size lenPupSoc.
// lclPort and frnPort point at port structures and may be omitted.
// If the local net and host or local socket are zero, they
// are defaulted to reasonable values (socket guaranteed unique).
// If the foreign net is zero, it is defaulted to be the same as
// the local net (which is assumed to be directly connected!).
// If transient then the PupSoc is expected to have only a short lifetime,
// and Pups arriving for this socket after it is closed should not be
// answered with Error Pups.  This works only if lclPort.socket is defaulted.
[
DefaultArgs(lv na, -1, 0, 0, false)
Zero(soc, lenPupSoc)
MoveBlock(lv soc>>PupSoc.psib, dPSIB, lenPSIB)
if lclPort ne 0 then MoveBlock(lv soc>>PupSoc.lclPort, lclPort, lenPort)
if frnPort ne 0 then MoveBlock(lv soc>>PupSoc.frnPort, frnPort, lenPort)
if soc>>PupSoc.lclPort.net eq 0 & soc>>PupSoc.lclPort.host eq 0 then
   [  // net and host unspecified, select reasonable default
   let ndb = nil
   compiletest multipleNets
      ifso
         [  // pick same net as foreign port, if possible
         let rte = HLookup(pupRT, soc>>PupSoc.frnPort.net)
         ndb = rte ne 0? rte>>RTE.ndb, ndbQ!0
         ]
      ifnot
         [  // only connected to one net so use that
         ndb = ndbQ!0
         ]
   soc>>PupSoc.lclPort.net = ndb>>NDB.localNet
   soc>>PupSoc.lclPort.host = ndb>>NDB.localHost
   ]
if soc>>PupSoc.lclPort.socket↑1 eq 0 & soc>>PupSoc.lclPort.socket↑2 eq 0 then
   [  // local socket unspecified, make a unique one
   soc>>PupSoc.lclPort.socket↑1 = transient? socTransient, soc
   soc>>PupSoc.lclPort.socket↑2 = socketSequence
   socketSequence = socketSequence+1
   ]
if soc>>PupSoc.frnPort.net eq 0 then  // foreign net unspecified
   soc>>PupSoc.frnPort.net = soc>>PupSoc.lclPort.net
Enqueue(socketQ, soc)
]

//----------------------------------------------------------------------------
and CloseLevel1Socket(soc) be
//----------------------------------------------------------------------------
[
unless Unqueue(socketQ, soc) do SysErr(soc, ecNoSuchSocket)

   [ // repeat
   FlushQueue(lv soc>>PupSoc.iQ)
   Block()
   ] repeatuntil soc>>PupSoc.numTPBI eq soc>>PupSoc.maxTPBI
]

//----------------------------------------------------------------------------
and FlushQueue(q) be while q!0 ne 0 do ReleasePBI(Dequeue(q))
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and SetAllocation(soc, total, input, output) be
//----------------------------------------------------------------------------
[
if input gr total % output gr total do SysErr(soc, ecBadAllocation)

// Devious means to add change in allocation to both "max" and "num"
// simultaneously.  Depends on max being in lh and num in rh.
soc>>PupSoc.tAll = soc>>PupSoc.tAll + (total - soc>>PupSoc.maxTPBI)*401B
soc>>PupSoc.iAll = soc>>PupSoc.iAll + (input - soc>>PupSoc.maxIPBI)*401B
soc>>PupSoc.oAll = soc>>PupSoc.oAll + (output - soc>>PupSoc.maxOPBI)*401B
]

//----------------------------------------------------------------------------
and AppendStringToPup(pbi, firstByte, string) be
//----------------------------------------------------------------------------
// Appends the given string to the pup, starting at content byte
// firstByte (i.e., pbi>>PBI.pup.bytes↑firstByte), and sets the
// pup length appropriately
[
for i = 1 to string>>String.length do
   pbi>>PBI.pup.bytes↑(firstByte+i-1) = string>>String.char↑i
pbi>>PBI.pup.length = pupOvBytes+firstByte+string>>String.length-1
]

compileif multipleNets then
[
external [ BroadcastNextNet; PupChecksum ]

//----------------------------------------------------------------------------
let BroadcastNextNet(pbi, ndb) be
//----------------------------------------------------------------------------
// Called from PupLevel1 and CompletePup for a pbi being broadcast
// on all directly connected networks
[
while ndb ne 0 do
   [
   unless ndb>>NDB.localNet eq 0 & pbi>>PBI.bypassZeroNet do
      [
      pbi>>PBI.ndb = ndb
      pbi>>PBI.pup.sPort.net = ndb>>NDB.localNet
      pbi>>PBI.pup.sPort.host = ndb>>NDB.localHost
      pbi>>PBI.pup.dPort.net = ndb>>NDB.localNet
      pbi!(lenPBIOverhead + (pbi>>PBI.pup.length-1) rshift 1) =
       pbi>>PBI.socket>>PupSoc.doChecksum? PupChecksum(lv pbi>>PBI.pup), -1
      ndb>>NDB.encapsulatePup(pbi, pbi>>PBI.pup.dPort.host)
      ndb>>NDB.level0Transmit(pbi)
      return
      ]
   ndb = ndb!0
   ]
ReleasePBI(pbi)
]

] // compileif multipleNets