package jexer.demos;
import java.net.*;
+import jexer.net.*;
/**
* This class is the main driver for a simple demonstration of Jexer's
}
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);
*/
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 --------------------------------------------------
*/
@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();
}
/**
*/
@Override
public void close() throws IOException {
- // TODO
+ if (readBuffer != null) {
+ readBuffer = null;
+ input.close();
+ }
}
/**
*/
@Override
public void mark(int readlimit) {
- // TODO
+ // Do nothing
}
/**
*/
@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;
}
/**
*/
@Override
public int read(byte[] b) throws IOException {
- // TODO
- return -1;
+ return read(b, 0, b.length);
}
/**
*/
@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;
}
/**
*/
@Override
public void reset() throws IOException {
- // TODO
+ throw new IOException("InputStream does not support mark/reset");
}
/**
*/
@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 --------------------------------------------------------
/**
buffer[1] = (byte)response;
buffer[2] = (byte)option;
- master.output.write(buffer);
+ output.rawWrite(buffer);
}
/**
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);
}
/**
* 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);
}
}
+ /**
+ * 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;
+ }
+
}
*/
@Override
public void close() throws IOException {
- // TODO
+ if (output != null) {
+ output.close();
+ output = null;
+ }
}
/**
*/
@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();
}
/**
*/
@Override
public void write(byte[] b) throws IOException {
- // TODO
+ writeImpl(b, 0, b.length);
}
/**
*/
@Override
public void write(byte[] b, int off, int len) throws IOException {
- // TODO
+ writeImpl(b, off, len);
}
/**
*/
@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);
+ }
+ }
+
+
}
boolean readCR;
// Flags used by the TelnetOutputStream
- int writeRC;
- int writeLastErrno;
- boolean writeLastError;
- boolean writeCR;
+ boolean writeCR;
/**
* Constuctor calls reset().
eofMsg = false;
readCR = false;
- writeRC = 0;
- writeLastErrno = 0;
- writeLastError = false;
writeCR = false;
-
}
}
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 -------------------------------------------------------
*/
@Override
public InputStream getInputStream() throws IOException {
- if (input == null) {
- input = new TelnetInputStream(this, super.getInputStream());
- }
return input;
}
*/
@Override
public OutputStream getOutputStream() throws IOException {
- if (output == null) {
- output = new TelnetOutputStream(this, super.getOutputStream());
- }
return output;
}