telnet support 75%
authorKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 25 Mar 2015 11:59:11 +0000 (07:59 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 25 Mar 2015 11:59:11 +0000 (07:59 -0400)
src/jexer/demos/Demo2.java
src/jexer/net/TelnetInputStream.java
src/jexer/net/TelnetOutputStream.java
src/jexer/net/TelnetSocket.java

index 35e40abc2c12062356d0fa40e20ddbfa65031ee0..c624c5a270558288bc33350f2d8dbb03dac74a55 100644 (file)
@@ -31,6 +31,7 @@
 package jexer.demos;
 
 import java.net.*;
+import jexer.net.*;
 
 /**
  * This class is the main driver for a simple demonstration of Jexer's
@@ -52,7 +53,7 @@ public class Demo2 {
             }
 
             int port = Integer.parseInt(args[0]);
-            ServerSocket server = new ServerSocket(port);
+            ServerSocket server = new TelnetServerSocket(port);
             while (true) {
                 Socket socket = server.accept();
                 System.out.printf("New connection: %s\n", socket);
index 0e2c012c135f8d08ec513cc422ca854cf8acc8bc..f409b7a6d17084fe021a9cbeb4920f1a087f6d4e 100644 (file)
@@ -53,15 +53,47 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     private InputStream input;
 
+    /**
+     * The telnet-aware OutputStream.
+     */
+    private TelnetOutputStream output;
+
+    /**
+     * Persistent read buffer.  In practice this will only be used if the
+     * single-byte read() is called sometime.
+     */
+    private byte [] readBuffer;
+
+    /**
+     * Current writing position in readBuffer - what is passed into
+     * input.read().
+     */
+    private int readBufferEnd;
+
+    /**
+     * Current read position in readBuffer - what is passed to the client in
+     * response to this.read().
+     */
+    private int readBufferStart;
+
     /**
      * Package private constructor.
      *
      * @param master the master TelnetSocket
      * @param input the underlying socket's InputStream
+     * @param output the telnet-aware OutputStream
      */
-    TelnetInputStream(TelnetSocket master, InputStream input) {
+    TelnetInputStream(final TelnetSocket master, final InputStream input,
+        final TelnetOutputStream output) {
+
         this.master = master;
         this.input  = input;
+        this.output = output;
+
+        // Setup new read buffer
+        readBuffer      = new byte[1024];
+        readBufferStart = 0;
+        readBufferEnd   = 0;
     }
 
     // SessionInfo interface --------------------------------------------------
@@ -157,8 +189,13 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public int available() throws IOException {
-        // TODO
-        return 0;
+        if (readBuffer == null) {
+            throw new IOException("InputStream is closed");
+        }
+        if (readBufferEnd - readBufferStart > 0) {
+            return (readBufferEnd - readBufferStart);
+        }
+        return input.available();
     }
 
     /**
@@ -167,7 +204,10 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public void close() throws IOException {
-        // TODO
+        if (readBuffer != null) {
+            readBuffer = null;
+            input.close();
+        }
     }
 
     /**
@@ -175,7 +215,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public void mark(int readlimit) {
-        // TODO
+        // Do nothing
     }
 
     /**
@@ -191,8 +231,31 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public int read() throws IOException {
-        // TODO
-        return -1;
+
+        // If the post-processed buffer has bytes, use that.
+        if (readBufferEnd - readBufferStart > 0) {
+            readBufferStart++;
+            return readBuffer[readBufferStart - 1];
+        }
+
+        // The buffer is empty, so reset the indexes to 0.
+        readBufferStart = 0;
+        readBufferEnd   = 0;
+
+        // Read some fresh data and run it through the telnet protocol.
+        int rc = readImpl(readBuffer, readBufferEnd,
+            readBuffer.length - readBufferEnd);
+
+        // If we got something, return it.
+        if (rc > 0) {
+            readBufferStart++;
+            return readBuffer[readBufferStart - 1];
+        }
+        // If we read 0, I screwed up big time.
+        assert (rc != 0);
+
+        // We read -1 (EOF).
+        return rc;
     }
 
     /**
@@ -201,8 +264,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public int read(byte[] b) throws IOException {
-        // TODO
-        return -1;
+        return read(b, 0, b.length);
     }
 
     /**
@@ -211,8 +273,41 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public int read(byte[] b, int off, int len) throws IOException {
-        // TODO
-        return -1;
+        // The only time we can return 0 is if len is 0, as per the
+        // InputStream contract.
+        if (len == 0) {
+            return 0;
+        }
+
+        // If the post-processed buffer has bytes, use that.
+        if (readBufferEnd - readBufferStart > 0) {
+            int n = Math.min(len, readBufferEnd - readBufferStart);
+            System.arraycopy(b, off, readBuffer, readBufferStart, n);
+            readBufferStart += n;
+            return n;
+        }
+
+        // The buffer is empty, so reset the indexes to 0.
+        readBufferStart = 0;
+        readBufferEnd   = 0;
+
+        // The maximum number of bytes we will ask for will definitely be
+        // within the bounds of what we can return in a single call.
+        int n = Math.min(len, readBuffer.length);
+
+        // Read some fresh data and run it through the telnet protocol.
+        int rc = readImpl(readBuffer, readBufferEnd, n);
+
+        // If we got something, return it.
+        if (rc > 0) {
+            System.arraycopy(readBuffer, 0, b, off, len);
+            return rc;
+        }
+        // If we read 0, I screwed up big time.
+        assert (rc != 0);
+
+        // We read -1 (EOF).
+        return rc;
     }
 
     /**
@@ -221,7 +316,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public void reset() throws IOException {
-        // TODO
+        throw new IOException("InputStream does not support mark/reset");
     }
 
     /**
@@ -229,11 +324,15 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      */
     @Override
     public long skip(long n) throws IOException {
-        // TODO
-        return -1;
+        if (n < 0) {
+            return 0;
+        }
+        for (int i = 0; i < n; i++) {
+            read();
+        }
+        return n;
     }
 
-
     // Telnet protocol --------------------------------------------------------
 
     /**
@@ -325,7 +424,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
         buffer[1] = (byte)response;
         buffer[2] = (byte)option;
 
-        master.output.write(buffer);
+        output.rawWrite(buffer);
     }
 
     /**
@@ -396,7 +495,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
         System.arraycopy(response, 0, buffer, 3, response.length);
         buffer[response.length + 3] = (byte)TELNET_IAC;
         buffer[response.length + 4] = (byte)TELNET_SE;
-        master.output.write(buffer);
+        output.rawWrite(buffer);
     }
 
     /**
@@ -455,7 +554,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * When run as a server:
      *     Echo
      */
-    private void telnetSendOptions() throws IOException {
+    void telnetSendOptions() throws IOException {
         if (master.nvt.binaryMode == false) {
             // Binary Transmission: must ask both do and will
             DO(0);
@@ -714,5 +813,15 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
         }
     }
 
+    /**
+     * Reads up to len bytes of data from the input stream into an array of
+     * bytes.
+     */
+    private int readImpl(byte[] b, int off, int len) throws IOException {
+        assert (len > 0);
+        // TODO
+        return -1;
+    }
+
 
 }
index b31cb03972a071cadda0b461b45ebf24a7a10f2b..98727277a8b47e6a5397d3ac3e77e131424edce8 100644 (file)
@@ -67,7 +67,10 @@ public final class TelnetOutputStream extends OutputStream {
      */
     @Override
     public void close() throws IOException {
-        // TODO
+        if (output != null) {
+            output.close();
+            output = null;
+        }
     }
 
     /**
@@ -76,6 +79,15 @@ public final class TelnetOutputStream extends OutputStream {
      */
     @Override
     public void flush() throws IOException {
+        if ((master.nvt.binaryMode == false) && (master.nvt.writeCR == true)) {
+            // The last byte sent to this.write() was a CR, which was never
+            // actually sent.  So send the CR in ascii mode, then flush.
+            // CR <anything> -> CR NULL
+            output.write(master.C_CR);
+            output.write(master.C_NUL);
+            master.nvt.writeCR = false;
+        }
+        output.flush();
     }
 
     /**
@@ -84,7 +96,7 @@ public final class TelnetOutputStream extends OutputStream {
      */
     @Override
     public void write(byte[] b) throws IOException {
-        // TODO
+        writeImpl(b, 0, b.length);
     }
 
     /**
@@ -93,7 +105,7 @@ public final class TelnetOutputStream extends OutputStream {
      */
     @Override
     public void write(byte[] b, int off, int len) throws IOException {
-        // TODO
+        writeImpl(b, off, len);
     }
 
     /**
@@ -101,7 +113,105 @@ public final class TelnetOutputStream extends OutputStream {
      */
     @Override
     public void write(int b) throws IOException {
-        // TODO
+        byte [] bytes = new byte[1];
+        bytes[0] = (byte)b;
+        writeImpl(bytes, 0, 1);
     }
 
+    /**
+     * Writes b.length bytes from the specified byte array to this output
+     * stream.  Note package private access.
+     */
+    void rawWrite(byte[] b) throws IOException {
+        output.write(b, 0, b.length);
+    }
+
+    /**
+     * Writes len bytes from the specified byte array starting at offset off
+     * to this output stream.
+     */
+    private void writeImpl(final byte[] b, final int off,
+        final int len) throws IOException {
+
+        byte [] writeBuffer = new byte[Math.max(len, 4)];
+        int writeBufferI = 0;
+
+        for (int i = 0; i < len; i++) {
+            if (writeBufferI >= writeBuffer.length - 4) {
+                // Flush what we have generated so far and reset the buffer,
+                // because the next byte could generate up to 4 output bytes
+                // (CR <something> <IAC> <IAC>).
+                super.write(writeBuffer, 0, writeBufferI);
+                writeBufferI = 0;
+            }
+
+            // Pull the next byte
+            byte ch = b[i + off];
+
+            if (master.nvt.binaryMode == true) {
+
+                if (ch == master.TELNET_IAC) {
+                    // IAC -> IAC IAC
+                    writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
+                    writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
+                } else {
+                    // Anything else -> just send
+                    writeBuffer[writeBufferI++] = ch;
+                }
+                continue;
+            }
+
+            // Non-binary mode: more complicated.  We use writeCR to handle
+            // the case that the last byte of b was a CR.
+
+            // Bare carriage return -> CR NUL
+            if (ch == master.C_CR) {
+                if (master.nvt.writeCR == true) {
+                    // Flush the previous CR to the stream.
+                    // CR <anything> -> CR NULL
+                    writeBuffer[writeBufferI++] = (byte)master.C_CR;
+                    writeBuffer[writeBufferI++] = (byte)master.C_NUL;
+                }
+                master.nvt.writeCR = true;
+            } else if (ch == master.C_LF) {
+                if (master.nvt.writeCR == true) {
+                    // CR LF -> CR LF
+                    writeBuffer[writeBufferI++] = (byte)master.C_CR;
+                    writeBuffer[writeBufferI++] = (byte)master.C_LF;
+                    master.nvt.writeCR = false;
+                } else {
+                    // Bare LF -> LF
+                    writeBuffer[writeBufferI++] = ch;
+                }
+            } else if (ch == master.TELNET_IAC) {
+                if (master.nvt.writeCR == true) {
+                    // CR <anything> -> CR NULL
+                    writeBuffer[writeBufferI++] = (byte)master.C_CR;
+                    writeBuffer[writeBufferI++] = (byte)master.C_NUL;
+                    master.nvt.writeCR = false;
+                }
+                // IAC -> IAC IAC
+                writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
+                writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
+            } else {
+                if (master.nvt.writeCR == true) {
+                    // CR <anything> -> CR NULL
+                    writeBuffer[writeBufferI++] = (byte)master.C_CR;
+                    writeBuffer[writeBufferI++] = (byte)master.C_NUL;
+                    master.nvt.writeCR = false;
+                } else {
+                    // Normal character */
+                    writeBuffer[writeBufferI++] = ch;
+                }
+            }
+
+        } // while (i < userbuf.length)
+
+        if (writeBufferI > 0) {
+            // Flush what we have generated so far and reset the buffer.
+            super.write(writeBuffer, 0, writeBufferI);
+        }
+    }
+
+
 }
index 6c1794c070ad7bd4b62fe9f056343b8f67362eaf..2724e54953409ff83670a1c6adb2cddc40f8bf19 100644 (file)
@@ -108,10 +108,7 @@ public final class TelnetSocket extends Socket {
         boolean readCR;
 
         // Flags used by the TelnetOutputStream
-        int writeRC;
-        int writeLastErrno;
-        boolean writeLastError;
-        boolean writeCR;
+       boolean writeCR;
 
         /**
          * Constuctor calls reset().
@@ -141,11 +138,7 @@ public final class TelnetSocket extends Socket {
             eofMsg              = false;
             readCR              = false;
 
-            writeRC             = 0;
-            writeLastErrno      = 0;
-            writeLastError      = false;
             writeCR             = false;
-
         }
     }
 
@@ -172,6 +165,12 @@ public final class TelnetSocket extends Socket {
         super();
         nvt = new TelnetState();
         this.socket = socket;
+
+        output = new TelnetOutputStream(this, super.getOutputStream());
+        input = new TelnetInputStream(this, super.getInputStream(), output);
+
+        // Initiate the telnet protocol negotiation.
+        input.telnetSendOptions();
     }
 
     // Socket interface -------------------------------------------------------
@@ -183,9 +182,6 @@ public final class TelnetSocket extends Socket {
      */
     @Override
     public InputStream getInputStream() throws IOException {
-        if (input == null) {
-            input = new TelnetInputStream(this, super.getInputStream());
-        }
         return input;
     }
 
@@ -196,9 +192,6 @@ public final class TelnetSocket extends Socket {
      */
     @Override
     public OutputStream getOutputStream() throws IOException {
-        if (output == null) {
-            output = new TelnetOutputStream(this, super.getOutputStream());
-        }
         return output;
     }