// IfsTelnetProt.bcpl -- BSP interface and Telnet protocol stuff
// Copyright Xerox Corporation 1979, 1980, 1981

// Last modified January 2, 1981  2:11 PM by Taft

get "Pup.decl"
get "IfsRs.decl"
get "IfsTelnet.decl"
get "IfsFiles.decl"

external
[
// outgoing procedures
TelnetGets; TelnetEndofs; TelnetResetIn
TelnetPuts; TelnetResetOut
TelnetProcess; TelnetErrors; TelnetOtherPup

// incoming procedures
BSPPutMark; BSPPutInterrupt; BSPForceOutput; BSPGetMark; ReleasePBI
ReadRingBuffer; WriteRingBuffer; ResetRingBuffer
RingBufferEmpty; RingBufferFull
Block; Yield; Dismiss; SetTimer; TimerHasExpired; Max; IFSError
Gets; Puts; Endofs; Errors

// incoming statics
CtxRunning; keys
]

// Stream operations on "keys" (the telnet input stream) and
// "dsp" (the telnet output stream)

//---------------------------------------------------------------------------
let TelnetGets(keys) = valof
//---------------------------------------------------------------------------
// Gets character from server context's telnet input ring buffer.
// If an abort (connection failure or control-C interrupt) has occurred,
// returns delete.  Also replies to timing marks and handles timeouts.
[
CtxRunning>>TCtx.thisLine = 0
let timer = nil
SetTimer(lv timer, telnetGetsTimeout)
   [ // repeat
   if CtxRunning>>TCtx.aborting resultis $*177
   let char = ReadRingBuffer(lv CtxRunning>>TCtx.iRBD)
   if char ge 0 then
      [
      if char eq #377 then
         [ BSPPutMark(CtxRunning>>TCtx.bspSoc, markTimingReply); loop ]
      resultis char
      ]

   // Inactivity timeout check (bypass if user is enabled wheel)
   if TimerHasExpired(lv timer) &
    not CtxRunning>>TCtx.userInfo>>UserInfo.capabilities.wheel then
      Errors(CtxRunning>>TCtx.bspStream, ecGetsTimeout)

   Dismiss(1)
   ] repeat
]

//---------------------------------------------------------------------------
and TelnetEndofs(keys) = RingBufferEmpty(lv CtxRunning>>TCtx.iRBD)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and TelnetResetIn(keys) be
//---------------------------------------------------------------------------
// Resets telnet input stream by invoking timing mark protocol.
[
ResetRingBuffer(lv CtxRunning>>TCtx.iRBD)
CtxRunning>>TCtx.oTimingMarks = CtxRunning>>TCtx.oTimingMarks+1
BSPPutMark(CtxRunning>>TCtx.bspSoc, markTiming)
until CtxRunning>>TCtx.oTimingMarks eq 0 do
   [ unless Endofs(keys) do Gets(keys); Dismiss(1) ]
]

//---------------------------------------------------------------------------
and TelnetPuts(dsp, char) be
//---------------------------------------------------------------------------
// Puts character onto Telnet BSP stream.  Flushing handled by TelnetProcess.
[
unless CtxRunning>>TCtx.aborting do
   [
   if char eq $*n then
      [
      CtxRunning>>TCtx.thisLine = CtxRunning>>TCtx.thisLine+1
      if CtxRunning>>TCtx.thisLine ge CtxRunning>>TCtx.numLine then
         [  // end of page, maybe pause for user
         if CtxRunning>>TCtx.pause & Endofs(keys) then
            [ Puts(dsp, #7); Gets(keys) ]
         CtxRunning>>TCtx.thisLine = 0
         ]
      ]
   if char ls #40 & char ne $*n & char ne #7 then
      [ Puts(dsp, $↑); char = char+#100 ]
   Puts(CtxRunning>>TCtx.bspStream, char, telnetPutsTimeout)
   if char eq $*n then  // insert lf after cr until Chat and Tenex fixed
      Puts(CtxRunning>>TCtx.bspStream, $*l, telnetPutsTimeout)
   ]
Yield()  // don't hog the machine
]

//---------------------------------------------------------------------------
and TelnetResetOut(dsp) be
//---------------------------------------------------------------------------
// Resets telnet output stream by invoking sync protocol.
[
BSPPutInterrupt(CtxRunning>>TCtx.bspSoc, 0, "")
BSPPutMark(CtxRunning>>TCtx.bspSoc, markSync)
CtxRunning>>TCtx.thisLine = 0
]

//---------------------------------------------------------------------------
and TelnetProcess(ctx) be
//---------------------------------------------------------------------------
// Performs processing for telnet context ctx.
// Specifically, transfers bsp input to typeahead buffer, interprets
// telnet protocol, and flushes the output stream.
[
unless ctx>>TCtx.initialized return
let soc, str = ctx>>TCtx.bspSoc, ctx>>TCtx.bspStream
if ctx>>TCtx.timeout return
if soc>>BSPSoc.state ne stateOpen then
   [ Errors(str, ecBadStateForGets); return ]
BSPForceOutput(soc)
   [ // repeat
   until Endofs(str) do
      [
      if RingBufferFull(lv ctx>>TCtx.iRBD) return
      let char = Gets(str)
      test ctx>>TCtx.pendingMark eq 0
         ifso test char eq $*003  // control-C?
            ifso
               [
               ResetRingBuffer(lv ctx>>TCtx.iRBD)
               ctx>>TCtx.controlC = true
               ]
            ifnot unless ctx>>TCtx.iSyncs gr 0 do
               WriteRingBuffer(lv ctx>>TCtx.iRBD, char&#177)
         ifnot
            [
            switchon ctx>>TCtx.pendingMark into
               [
               case markPageLength:
                  [ ctx>>TCtx.numLine = Max(char, 5); endcase ]
               case markTerminalType:
                  [ ctx>>TCtx.pause = char eq termTypeDisplay; endcase ]
               ]
            ctx>>TCtx.pendingMark = 0
            ]
      ]
   unless soc>>BSPSoc.markPending break
   let mark = BSPGetMark(soc)
   switchon mark into
      [
      case markSync:
         [ ctx>>TCtx.iSyncs = ctx>>TCtx.iSyncs-1; endcase ]
      case markTiming:
         [ WriteRingBuffer(lv ctx>>TCtx.iRBD, #377); endcase ]
      case markTimingReply:
         [ ctx>>TCtx.oTimingMarks = ctx>>TCtx.oTimingMarks-1; endcase ]
      case markLineWidth:
      case markPageLength:
      case markTerminalType:
         [ ctx>>TCtx.pendingMark = mark; endcase ]
      ]
   ] repeat
]

//---------------------------------------------------------------------------
and TelnetErrors(str, ec) = valof
//---------------------------------------------------------------------------
// Handles errors occurring on the telnet connection.
// This occurs in the RsMgr context for Gets and the server context for Puts.
[
let ctx = str>>BSPStr.par1
ctx>>TCtx.iSyncs = 0  // reset protocol interactions
ctx>>TCtx.oTimingMarks = 0
switchon ec into
   [
   case ecGetsTimeout:
   case ecPutsTimeout:
      ctx>>TCtx.timeout = true
   case ecBadStateForGets:
   case ecBadStateForPuts:
      ctx>>TCtx.logout = true
      resultis -1
   default:
      IFSError(ecUncaughtTelnetError, ec)
   ]
]


//---------------------------------------------------------------------------
and TelnetOtherPup(pbi) be
//---------------------------------------------------------------------------
// Handles special Pups (specifically Interrupts) that pop out of the
// Telnet socket.  Note that this is called from within the socket's RTP
// context.  However, it is safe for this procedure to be in an overlay
// because if any socket is open then there is a process executing inside
// TelnetTopLevel, which is in the same overlay.
[
if pbi>>PBI.pup.type eq typeInterrupt then
   [
   let ctx = pbi>>PBI.socket>>BSPSoc.par1
   ctx>>TCtx.iSyncs = ctx>>TCtx.iSyncs+1
   if ctx>>TCtx.iSyncs gr 0 then ResetRingBuffer(lv ctx>>TCtx.iRBD)
   ]
ReleasePBI(pbi)
]