From 005ec49709aab8590ee9571f17a67440969c6248 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 25 Mar 2015 07:59:11 -0400 Subject: [PATCH] telnet support 75% --- src/jexer/demos/Demo2.java | 3 +- src/jexer/net/TelnetInputStream.java | 145 ++++++++++++++++++++++---- src/jexer/net/TelnetOutputStream.java | 118 ++++++++++++++++++++- src/jexer/net/TelnetSocket.java | 21 ++-- 4 files changed, 250 insertions(+), 37 deletions(-) diff --git a/src/jexer/demos/Demo2.java b/src/jexer/demos/Demo2.java index 35e40ab..c624c5a 100644 --- a/src/jexer/demos/Demo2.java +++ b/src/jexer/demos/Demo2.java @@ -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); diff --git a/src/jexer/net/TelnetInputStream.java b/src/jexer/net/TelnetInputStream.java index 0e2c012..f409b7a 100644 --- a/src/jexer/net/TelnetInputStream.java +++ b/src/jexer/net/TelnetInputStream.java @@ -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; + } + } diff --git a/src/jexer/net/TelnetOutputStream.java b/src/jexer/net/TelnetOutputStream.java index b31cb03..9872727 100644 --- a/src/jexer/net/TelnetOutputStream.java +++ b/src/jexer/net/TelnetOutputStream.java @@ -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 -> 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 ). + 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 -> 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 -> 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 -> 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); + } + } + + } diff --git a/src/jexer/net/TelnetSocket.java b/src/jexer/net/TelnetSocket.java index 6c1794c..2724e54 100644 --- a/src/jexer/net/TelnetSocket.java +++ b/src/jexer/net/TelnetSocket.java @@ -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; } -- 2.27.0