-- Transport mechanism: Telnet interface

-- [Indigo]<Grapevine>MS>GlassImpl.mesa

-- Andrew Birrell 17-May-82  9:59:51
-- Mark Johnson  19-May-81 13:28:28
-- Mike Schroeder,   7-Feb-83 16:00:56

DIRECTORY
Ascii,
GlassDefs	USING[ HandleObject, Handle, StringType ],
Inline	USING[ BITAND, BITOR ],
PolicyDefs	USING[ CheckOperation, EndOperation, Operation ],
Process	USING[ Pause, SecondsToTicks, SetTimeout, Yield ],
PupDefs		USING[ PupAddress, PupSocketID ],
PupStream	USING[ CreatePupByteStreamListener, RejectThisRequest,
		       StreamClosing, veryLongWait ],
PupTypes	USING[ telnetSoc ],
Stream,
String	USING[ AppendDecimal, AppendLongDecimal ];

GlassImpl: MONITOR LOCKS slp USING slp: POINTER TO MONITORLOCK
   IMPORTS Inline, PolicyDefs, Process, PupStream, Stream, String
   EXPORTS GlassDefs =

BEGIN

TimeOut:      PUBLIC SIGNAL = CODE;
SynchReply:   PUBLIC SIGNAL = CODE;
ReaderDied:   ERROR = CODE;
MyStreamClosing: ERROR = CODE;

ReadString: PUBLIC PROC[str: GlassDefs.Handle, prompt: STRING,
                        s: STRING, type: GlassDefs.StringType]
                RETURNS[end: CHARACTER] =
   BEGIN
   OPEN str;
   dummy: CHARACTER = '*; --used for echo if type = pwd--
   ShowIt: PROC =
      BEGIN
      IF type # pwd
      THEN WriteString[s]
      ELSE FOR i: CARDINAL IN [0..s.length) DO WriteChar[dummy] ENDLOOP;
      END;
   Unwrite: PROCEDURE =
      BEGIN
      IF s.length > 0
      THEN BEGIN
           WriteChar['\];
           WriteChar[IF type = pwd THEN dummy ELSE s[s.length-1]];
           s.length ← s.length - 1;
           END;
      END;
   ClearWord: PROCEDURE =
      BEGIN
      state: { alpha, other } ← other;
      WHILE s.length > 0
      DO SELECT s[s.length-1] FROM
           IN ['a..'z], IN ['A..'Z], IN ['0..'9] => state ← alpha;
         ENDCASE => IF state # other THEN EXIT;
         Unwrite[];
      ENDLOOP;
      END;
   c: CHARACTER;
   WriteString[prompt]; ShowIt[];
   c ← ReadChar[];
   SELECT c FROM
     Ascii.ControlA, Ascii.BS, Ascii.ControlW, --client wants to edit it--
     Ascii.SP, Ascii.CR, Ascii.DEL => NULL; --client accepts it--
   ENDCASE =>
     IF s.length > 0
     THEN { WriteString["← "L]; s.length ← 0; };--client rejects it--
   DO SELECT c FROM
         Ascii.ControlA, Ascii.BS => Unwrite[];
         Ascii.ControlW => ClearWord[];
         Ascii.ControlR =>
           { WriteChar[Ascii.CR]; WriteString[prompt]; ShowIt[] };
      ENDCASE =>
        BEGIN
        SELECT c FROM
          Ascii.SP => IF type # line AND type # any THEN { end ← c; EXIT };
          Ascii.CR => IF type # any THEN { end ← c; EXIT };
          Ascii.ESC, Ascii.DEL => { end ← c; EXIT };
        ENDCASE => NULL;
        IF s.length < s.maxlength
        THEN BEGIN
             s[s.length] ← c; s.length ← s.length+1;
             WriteChar[IF type = pwd THEN dummy ELSE c];
             END
        ELSE WriteChar[Ascii.BEL];
        END;
      c ← ReadChar[];
   ENDLOOP;
   END;

Listen: PUBLIC PROC[ work: PROC[GlassDefs.Handle],
                           socket: PupDefs.PupSocketID ← [0,0] ] =
   BEGIN
   TelnetWork: PROC[ str: Stream.Handle, from: PupDefs.PupAddress ] =
      BEGIN
      -- Note that there is an over-all assumption that the client calls
      -- the glass stream from only one process.  The monitor locks are
      -- used only to synchronize between the "Reader" process and the
      -- client.
      strLock: MONITORLOCK; -- lock for this stream --
      readerPSB: PROCESS;
      readerWanted: BOOLEAN ← TRUE;
      -- we maintain a circular buffer of incoming characters,
      -- primarily to look ahead for DEL --
      -- rPos = next index for reading from buffer --
      -- wPos = next index for writing into buffer --
      -- rPos = wPos iff buffer is empty --
      -- (wPos+1)MOD bLength = rPos iff buffer is full --
      -- buffer data has "markBit" on iff datum is a "Mark" byte --
      charMask:     WORD = 177B;
      markBit:      WORD = 200B;
      bLength:      CARDINAL = 100;
      buffer:       PACKED ARRAY [0..bLength) OF CHARACTER;
      rPos:         CARDINAL ← 0;
      wPos:         CARDINAL ← 0;
      bFuller:      CONDITION;
      bEmptier:     CONDITION;
      delCount:     CARDINAL ← 0;
      charsWritten: BOOLEAN ← FALSE;--chars written but not sent--
      readerDead:   BOOLEAN ← FALSE;
      NoteDeadReader: ENTRY PROC [slp: POINTER TO MONITORLOCK] =
         {readerDead ← TRUE; NOTIFY bFuller};
      ChangeWPos: ENTRY PROC[change: CARDINAL,
            slp: POINTER TO MONITORLOCK] = INLINE
         BEGIN
         ENABLE UNWIND => NULL;
         IF rPos = wPos THEN NOTIFY bFuller;
         wPos ← wPos + change; IF wPos = bLength THEN wPos ← 0;
         END;
      WLimit: ENTRY PROC [slp: POINTER TO MONITORLOCK]
            RETURNS[ limit: CARDINAL ] = INLINE
         BEGIN
         ENABLE UNWIND => NULL;
         WHILE (limit ← IF wPos >= rPos
                        THEN IF rPos = 0 THEN bLength-1 ELSE bLength
                        ELSE rPos-1 )
               = wPos
         DO WAIT bEmptier ENDLOOP;
         END;
      AddDel: ENTRY PROC [slp: POINTER TO MONITORLOCK] =
         BEGIN
         ENABLE UNWIND => NULL;
         delCount ← delCount + 1;
         Stream.SendAttention[str, 0];
         Stream.SetSST[str, 1 --data mark--];
         END;
      GetByte: ENTRY PROC [slp: POINTER TO MONITORLOCK]
            RETURNS[ c: UNSPECIFIED ] =
         BEGIN
         ENABLE UNWIND => NULL;
         WHILE rPos = wPos
         DO IF charsWritten
            THEN { charsWritten ← FALSE; Stream.SendNow[str] };
            IF readerDead THEN ERROR ReaderDied[];
            WAIT bFuller;
            IF rPos = wPos THEN SIGNAL TimeOut[];
         ENDLOOP;
         c ← buffer[rPos];
         rPos ← rPos+1; IF rPos = bLength THEN rPos ← 0;
         NOTIFY bEmptier; -- in case buffer was full --
         IF c = Ascii.DEL THEN delCount ← delCount-1;
         END;
      TerminateReader: ENTRY PROC [slp: POINTER TO MONITORLOCK] =
         BEGIN
         -- ensure reader isn't waiting because of a full input buffer --
         UNTIL readerDead
         DO rPos ← wPos ← 0; NOTIFY bEmptier; WAIT bFuller ENDLOOP;
         END;
      Reader: PROC =
         BEGIN
         Stream.SetInputOptions[str,
            [terminateOnEndPhysicalRecord: TRUE,
             signalLongBlock: FALSE,
             signalShortBlock: FALSE,
             signalSSTChange: FALSE,
             signalEndOfStream: FALSE] ];
         DO ENABLE
               BEGIN
               Stream.TimeOut => RESUME;
               PupStream.StreamClosing => EXIT;
               END;
            used: CARDINAL;
            why: Stream.CompletionCode;
            sst: Stream.SubSequenceType;
            [used, why, sst] ← Stream.GetBlock[str,
               [ @buffer, wPos, WLimit[@strLock] ] ];
            FOR index: CARDINAL IN [wPos..wPos+used)
            DO SELECT (buffer[index]←Inline.BITAND[buffer[index], charMask])
               FROM
                 Ascii.ControlC => { buffer[index] ← Ascii.DEL; AddDel[@strLock] };
                 Ascii.DEL => AddDel[@strLock];
               ENDCASE => NULL;
            ENDLOOP;
            ChangeWPos[used, @strLock];
            IF why = sstChange
            THEN BEGIN
                 buffer[wPos] ← Inline.BITOR[sst, markBit];
                 ChangeWPos[1, @strLock];
                 IF sst = 6 --timing mark reply-- AND NOT readerWanted
                 THEN EXIT;
                 END;
         ENDLOOP;
         NoteDeadReader[@strLock];
         END;
      lineWidth:  CARDINAL ← 0;
      pageHeight: CARDINAL ← 0;
      terminal:   CARDINAL ← 0;
      charPos:    CARDINAL ← 0;
      linePos:    CARDINAL ← 0;
      ConsiderSST: PROC[thisSST: Stream.SubSequenceType] =
         BEGIN
         SELECT thisSST FROM
            1 => -- data mark -- NULL;
            2 => lineWidth ← GetByte[@strLock];
            3 => pageHeight ← GetByte[@strLock];
            4 => terminal ← GetByte[@strLock];
            5 => Stream.SetSST[str, 6--timing mark reply--];
            6 => SIGNAL SynchReply[];
         ENDCASE => NULL--ignore--;
         END;
      ReadChar: PROC RETURNS[c: CHARACTER] =
         BEGIN
         ENABLE PupStream.StreamClosing => ERROR MyStreamClosing;
         DO c ← GetByte[@strLock];
            IF Inline.BITAND[c, markBit] # 0
            THEN ConsiderSST[Inline.BITAND[c, charMask]]
            ELSE EXIT
         ENDLOOP;
         linePos ← 0; -- only count lines between input operations --           
         END;
      MyReadString: PROC[prompt: STRING,
                         s: STRING, type: GlassDefs.StringType]
                 RETURNS[end: CHARACTER] =
         { end ← ReadString[@obj, prompt, s, type] };
      WriteChar: PROC[c: CHARACTER] =
         -- assumed to be called from only one process --
         -- otherwise, we need two monitor locks: this may use ReadChar --
         BEGIN
         ENABLE PupStream.StreamClosing => ERROR MyStreamClosing;
         WS: PROC[s: STRING] =
            BEGIN -- sneak in a string --
            FOR index: CARDINAL IN [0..s.length)
            WHILE charPos < lineWidth
            DO PutSingleWidth[s[index]] ENDLOOP;
            END;
         Lf: PROC =
            BEGIN
            IF linePos+1 >= pageHeight AND pageHeight # 0
            THEN BEGIN
                 IF charPos > 0 THEN Stream.PutChar[str, Ascii.CR];
                 charPos ← 0;
                 Stream.PutChar[str, Ascii.LF];
                 WS["Type ESC for next page ..."L]; SendNow[];
                 UNTIL ReadChar[] = Ascii.ESC DO ENDLOOP;
                 Stream.PutChar[str, Ascii.CR];
                 charPos ← 0;
                 END;
            Stream.PutChar[str, Ascii.LF];
            linePos←linePos+1;
            END;
         Newline: PROC =
            BEGIN
            Stream.PutChar[str, Ascii.CR];
            charPos←0;
            Lf[];
            END;
         PutSingleWidth: PROC[c: CHARACTER] = INLINE
            BEGIN
            IF charPos = lineWidth AND lineWidth > 0 THEN Newline[];
            Stream.PutChar[str, c]; charPos ← charPos+1;
            END;
         NoteWritten: ENTRY PROC [slp: POINTER TO MONITORLOCK] =
            INLINE { charsWritten ← TRUE };
         Process.Yield[];
         IF delCount # 0 THEN RETURN;
         SELECT c FROM
           IN [40C..177C] => PutSingleWidth[c];
           Ascii.CR => Newline[];
           Ascii.LF => Lf[];
           Ascii.BEL => Stream.PutChar[str, c];
           Ascii.TAB =>
             DO PutSingleWidth[Ascii.SP]; IF charPos MOD 8 = 0 THEN EXIT;
             ENDLOOP;
           IN [0C..40C) =>
             { PutSingleWidth['↑]; PutSingleWidth[c+100B] };
         ENDCASE => NULL -- illegal character values --;
         NoteWritten[@strLock];
         END;
      WriteString: PROC[s: STRING] =
         { FOR i: CARDINAL IN [0..s.length) DO WriteChar[s[i]] ENDLOOP };
      WriteDecimal: PROC[n: CARDINAL] =
         BEGIN
         s: STRING = [6] -- -65536 --;
         String.AppendDecimal[s,n];
         WriteString[s];
         END;
      WriteLongDecimal: PROC[n: LONG CARDINAL] =
         BEGIN
         s: STRING = [11] -- -6553665536 --;
         String.AppendLongDecimal[s,n];
         WriteString[s];
         END;
      NoteSent: ENTRY PROC [slp: POINTER TO MONITORLOCK] = INLINE
         { charsWritten ← FALSE };
      SendNow: PROC =
         { ENABLE PupStream.StreamClosing => ERROR MyStreamClosing;
            NoteSent[@strLock]; Stream.SendNow[str ] };
      CharsLeft: PROC RETURNS[CARDINAL] =
         { RETURN[IF lineWidth > 0
                  THEN lineWidth-charPos ELSE LAST[CARDINAL] ] };
      LinesLeft: PROC RETURNS[CARDINAL] =
         { RETURN[IF pageHeight > 0
                  THEN pageHeight-linePos ELSE LAST[CARDINAL] ] };
      SetWidth: PROC[new: CARDINAL] =
         { lineWidth ← new };
      SetHeight: PROC[new: CARDINAL] =
         { pageHeight ← new };
      DelTyped: PROC RETURNS [BOOLEAN] =
         BEGIN
         InnerDelTyped: ENTRY PROC [slp: POINTER TO MONITORLOCK]
            RETURNS[ BOOLEAN ] = INLINE { RETURN[ delCount # 0 ] };
         RETURN [InnerDelTyped[@strLock]]
         END;
      Synch: PROC =
         { ENABLE PupStream.StreamClosing => ERROR MyStreamClosing;
            Stream.SetSST[str,5]  };
      Flush: PROC =
         { WHILE delCount > 0 DO [] ← ReadChar[] ENDLOOP };
      obj: GlassDefs.HandleObject ← [ReadChar, MyReadString,
                                     WriteChar, WriteString,
                                     WriteDecimal, WriteLongDecimal,
                                     SendNow, CharsLeft, LinesLeft,
                                     SetWidth, SetHeight,
                                     DelTyped, Synch, Flush];
      Process.SetTimeout[@bFuller,
            Process.SecondsToTicks[300] ];
      readerPSB ← FORK Reader[];
      BEGIN
         ENABLE{ MyStreamClosing, ReaderDied => CONTINUE;
                 TimeOut => RESUME };
         work[@obj ! SynchReply => RESUME];
         readerWanted ← FALSE; Synch[];
         DO [] ← ReadChar[ ! SynchReply => EXIT ] ENDLOOP;
      END;
      TerminateReader[@strLock];
      JOIN readerPSB;
      str.delete[str];
      PolicyDefs.EndOperation[op];
      END;
   TelnetFilter: PROC[addr: PupDefs.PupAddress] =
      BEGIN
      IF NOT PolicyDefs.CheckOperation[op]
      THEN PupStream.RejectThisRequest["Server full"L];
      END;
   op: PolicyDefs.Operation = IF socket = [0,53B] THEN lily ELSE telnet;
   [] ← PupStream.CreatePupByteStreamListener[
           IF socket = [0,0] THEN PupTypes.telnetSoc ELSE socket,
           TelnetWork, PupStream.veryLongWait, TelnetFilter ];
   DO Process.Pause[Process.SecondsToTicks[600]] ENDLOOP;
   END;
   

END.