// DLSTelnet.bcpl -- Telnet server providing dial-out capability

// Last modified October 20, 1983  2:14 PM by Diebert

get "DLSDriver.decl"
get "Pup.decl"
get "DLSControl.decl"

external
[
// Outgoing procedures
DLSSocketNotFound; TelnetRendezvousServer; DialOutTop; SendAbort; GetNumber
GenerateLongSpace; TelnetGets; InnerGets; TelnetEndofs; TelnetResets
TelnetPuts; SendTelnetProtocol


// Incoming procedures
HangUp; Echo; Confirm; Error; GetString; LeaveRemoteMode; DialOut; OpenPortForCtx
ControlOut; SetDLSLineSpeed; DLSOutput7; DLSResetInput; DLSResetOutput
DLSInputIdle; WaitForBitTimes; PrintLogLine
ExchangePorts; AppendStringToPup; CompletePup; ReleasePBI; PupError
OpenRTPSocket; CreateBSPStream; BSPGetMark; BSPPutMark; BSPForceOutput
InitializeContext; Block; Dismiss
Enqueue; Dequeue; Unqueue; SetTimer; TimerHasExpired; MultEq
Gets; Puts; Endofs; Errors; Wss; PutTemplate
MoveBlock; SetBlock; SysErr; ReturnFrom; MyFrame; DialOutBSPError
DLSInput; DLSInput7; DLSOutput; ControlIn
Login; DialOutDialIn; Free; StatusToLog; CallSwat

// Outgoing statics
strayPupQ

// Incoming statics
dlsName; dlsRegistry; dlsAuthorizationList; dlsGVhasnotInit 
@lbTable; ctxTable; mainCtx; CtxRunning; postedNotice; crlf; ndbQ; sysZone
logstream
]


manifest
[
// Telnet mark types
markSync = 1
markLineWidth = 2
markPageLength = 3
markTerminalType = 4
markTiming = 5
markTimingReply = 6
]

structure AuxCtx:	// Auxiliary (DialOutLineToNet) context
[
blank word 3		// usual Ctx stuff
mainCtx word		// -> main context for line
active word		// true if recent activity in net -> line direction
]


// ---------------------------------------------------------------------------
let GenerateLongSpace(dlb, bitTimes) be
// ---------------------------------------------------------------------------
[
while dlb>>DLB.outActive do Block()
dlsOutBase!(dlb>>DLB.oLCB.line) = 0  // space = EIA high
Dismiss((bitTimes*(dlb>>DLB.oLCB.interval+1))/((dlsTicksPerSecond+50)/100))
dlsOutBase!(dlb>>DLB.oLCB.line) = 1  // mark = EIA low
]

// ---------------------------------------------------------------------------
and TelnetGets(tstr, remoteMode; numargs na) = valof
// ---------------------------------------------------------------------------
// The Gets procedure for the Telnet stream.  It interprets Telnet protocol.
// If remoteMode is false or omitted, times out after 2 minutes (at which point
// Errors is called), and also forces BSP output periodically.
// If remoteMode is true, no timeout or forcing occurs.
[
if na ls 2 then remoteMode = false
let ctx = tstr>>ST.par1
let soc = lv ctx>>CTX.socket
let str = lv soc>>BSPSoc.bspStr
let char = InnerGets(str, remoteMode)
if char ge 0 resultis char

// Encountered a mark.  Interpret Telnet protocol.
switchon BSPGetMark(soc) into
   [
   case markTiming:  // Should really await DLSOutputEmpty, but not easy!
      [ BSPPutMark(soc, markTimingReply); endcase ]
   case markLineWidth:
   case markPageLength:
   case markTerminalType:  // For now, ignore terminal parameters
      [ InnerGets(str, remoteMode); endcase ]

   // Just ignore other mark types.
   // Shouldn't ever receive markTimingReply because we never send markTiming.
   // Ignore markSync since it isn't typically generated by user Telnet.
   ]
] repeat

// ---------------------------------------------------------------------------
and InnerGets(str, remoteMode) = valof
// ---------------------------------------------------------------------------
// Returns the next byte from BSP stream str, or -1 if a mark is encountered.
// remoteMode is as for TelnetGets, except that it is not defaulted.
[
if remoteMode resultis Gets(str)
let soc = str - offset BSPSoc.bspStr/16
let timer = nil
SetTimer(lv timer, 12000)  // 2-minute timeout for local mode
until TimerHasExpired(lv timer) do
   [
   if soc>>BSPSoc.state ne stateOpen then Errors(str, ecBadStateForGets)
   unless Endofs(str) resultis Gets(str)
   if soc>>BSPSoc.markPending resultis -1

   // Force Telnet output every 40 ms while we are waiting for input
   BSPForceOutput(soc)
   Dismiss(4)
   ]
Errors(str, ecGetsTimeout)
]

// ---------------------------------------------------------------------------
and TelnetEndofs(tstr) = Endofs(lv tstr>>ST.par1>>CTX.socket.bspStr)
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
and TelnetResets(tstr) be until Endofs(tstr) do Gets(tstr)
// ---------------------------------------------------------------------------
// Reset Telnet input.
// Should really use Telnet timing mark to flush input stream.

// ---------------------------------------------------------------------------
and TelnetPuts(tstr, char) be Puts(lv tstr>>ST.par1>>CTX.socket.bspStr, char)
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
and SendTelnetProtocol(soc, markByte, argByte) be
// ---------------------------------------------------------------------------
[
BSPPutMark(soc, markByte)
Puts(lv soc>>BSPSoc.bspStr, argByte, 3000)
BSPForceOutput(soc)
]