panda.net.telnet.TelnetInputStream Maven / Gradle / Ivy
Show all versions of panda-core Show documentation
package panda.net.telnet;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
final class TelnetInputStream extends BufferedInputStream implements Runnable {
/** End of file has been reached */
private static final int EOF = -1;
/** Read would block */
private static final int WOULD_BLOCK = -2;
// TODO should these be private enums?
static final int _STATE_DATA = 0, _STATE_IAC = 1, _STATE_WILL = 2, _STATE_WONT = 3, _STATE_DO = 4, _STATE_DONT = 5,
_STATE_SB = 6, _STATE_SE = 7, _STATE_CR = 8, _STATE_IAC_SB = 9;
private boolean __hasReachedEOF; // @GuardedBy("__queue")
private volatile boolean __isClosed;
private boolean __readIsWaiting;
private int __receiveState, __queueHead, __queueTail, __bytesAvailable;
private final int[] __queue;
private final TelnetClient __client;
private final Thread __thread;
private IOException __ioException;
/* TERMINAL-TYPE option (start) */
private final int __suboption[] = new int[512];
private int __suboption_count = 0;
/* TERMINAL-TYPE option (end) */
private volatile boolean __threaded;
TelnetInputStream(InputStream input, TelnetClient client, boolean readerThread) {
super(input);
__client = client;
__receiveState = _STATE_DATA;
__isClosed = true;
__hasReachedEOF = false;
// Make it 2049, because when full, one slot will go unused, and we
// want a 2048 byte buffer just to have a round number (base 2 that is)
__queue = new int[2049];
__queueHead = 0;
__queueTail = 0;
__bytesAvailable = 0;
__ioException = null;
__readIsWaiting = false;
__threaded = false;
if (readerThread) {
__thread = new Thread(this);
}
else {
__thread = null;
}
}
TelnetInputStream(InputStream input, TelnetClient client) {
this(input, client, true);
}
void _start() {
if (__thread == null) {
return;
}
int priority;
__isClosed = false;
// TODO remove this
// Need to set a higher priority in case JVM does not use pre-emptive
// threads. This should prevent scheduler induced deadlock (rather than
// deadlock caused by a bug in this code).
priority = Thread.currentThread().getPriority() + 1;
if (priority > Thread.MAX_PRIORITY) {
priority = Thread.MAX_PRIORITY;
}
__thread.setPriority(priority);
__thread.setDaemon(true);
__thread.start();
__threaded = true; // tell _processChar that we are running threaded
}
// synchronized(__client) critical sections are to protect against
// TelnetOutputStream writing through the telnet client at same time
// as a processDo/Will/etc. command invoked from TelnetInputStream
// tries to write.
/**
* Get the next byte of data. IAC commands are processed internally and do not return data.
*
* @param mayBlock true if method is allowed to block
* @return the next byte of data, or -1 (EOF) if end of stread reached, or -2 (WOULD_BLOCK) if
* mayBlock is false and there is no data available
*/
private int __read(boolean mayBlock) throws IOException {
int ch;
while (true) {
// If there is no more data AND we were told not to block,
// just return WOULD_BLOCK (-2). (More efficient than exception.)
if (!mayBlock && super.available() == 0) {
return WOULD_BLOCK;
}
// Otherwise, exit only when we reach end of stream.
if ((ch = super.read()) < 0) {
return EOF;
}
ch = (ch & 0xff);
/* Code Section added for supporting AYT (start) */
synchronized (__client) {
__client._processAYTResponse();
}
/* Code Section added for supporting AYT (end) */
/* Code Section added for supporting spystreams (start) */
__client._spyRead(ch);
/* Code Section added for supporting spystreams (end) */
switch (__receiveState) {
case _STATE_CR:
if (ch == '\0') {
// Strip null
continue;
}
// How do we handle newline after cr?
// else if (ch == '\n' && _requestedDont(TelnetOption.ECHO) &&
// Handle as normal data by falling through to _STATE_DATA case
//$FALL-THROUGH$
case _STATE_DATA:
if (ch == TelnetCommand.IAC) {
__receiveState = _STATE_IAC;
continue;
}
if (ch == '\r') {
synchronized (__client) {
if (__client._requestedDont(TelnetOption.BINARY)) {
__receiveState = _STATE_CR;
}
else {
__receiveState = _STATE_DATA;
}
}
}
else {
__receiveState = _STATE_DATA;
}
break;
case _STATE_IAC:
switch (ch) {
case TelnetCommand.WILL:
__receiveState = _STATE_WILL;
continue;
case TelnetCommand.WONT:
__receiveState = _STATE_WONT;
continue;
case TelnetCommand.DO:
__receiveState = _STATE_DO;
continue;
case TelnetCommand.DONT:
__receiveState = _STATE_DONT;
continue;
/* TERMINAL-TYPE option (start) */
case TelnetCommand.SB:
__suboption_count = 0;
__receiveState = _STATE_SB;
continue;
/* TERMINAL-TYPE option (end) */
case TelnetCommand.IAC:
__receiveState = _STATE_DATA;
break; // exit to enclosing switch to return IAC from read
case TelnetCommand.SE: // unexpected byte! ignore it (don't send it as a command)
__receiveState = _STATE_DATA;
continue;
default:
__receiveState = _STATE_DATA;
__client._processCommand(ch); // Notify the user
continue; // move on the next char
}
break; // exit and return from read
case _STATE_WILL:
synchronized (__client) {
__client._processWill(ch);
__client._flushOutputStream();
}
__receiveState = _STATE_DATA;
continue;
case _STATE_WONT:
synchronized (__client) {
__client._processWont(ch);
__client._flushOutputStream();
}
__receiveState = _STATE_DATA;
continue;
case _STATE_DO:
synchronized (__client) {
__client._processDo(ch);
__client._flushOutputStream();
}
__receiveState = _STATE_DATA;
continue;
case _STATE_DONT:
synchronized (__client) {
__client._processDont(ch);
__client._flushOutputStream();
}
__receiveState = _STATE_DATA;
continue;
/* TERMINAL-TYPE option (start) */
case _STATE_SB:
switch (ch) {
case TelnetCommand.IAC:
__receiveState = _STATE_IAC_SB;
continue;
default:
// store suboption char
if (__suboption_count < __suboption.length) {
__suboption[__suboption_count++] = ch;
}
break;
}
__receiveState = _STATE_SB;
continue;
case _STATE_IAC_SB: // IAC received during SB phase
switch (ch) {
case TelnetCommand.SE:
synchronized (__client) {
__client._processSuboption(__suboption, __suboption_count);
__client._flushOutputStream();
}
__receiveState = _STATE_DATA;
continue;
case TelnetCommand.IAC: // De-dup the duplicated IAC
if (__suboption_count < __suboption.length) {
__suboption[__suboption_count++] = ch;
}
break;
default: // unexpected byte! ignore it
break;
}
__receiveState = _STATE_SB;
continue;
/* TERMINAL-TYPE option (end) */
}
break;
}
return ch;
}
// synchronized(__client) critical sections are to protect against
// TelnetOutputStream writing through the telnet client at same time
// as a processDo/Will/etc. command invoked from TelnetInputStream
// tries to write. Returns true if buffer was previously empty.
private boolean __processChar(int ch) throws InterruptedException {
// Critical section because we're altering __bytesAvailable,
// __queueTail, and the contents of _queue.
boolean bufferWasEmpty;
synchronized (__queue) {
bufferWasEmpty = (__bytesAvailable == 0);
while (__bytesAvailable >= __queue.length - 1) {
// The queue is full. We need to wait before adding any more data to it. Hopefully
// the stream owner
// will consume some data soon!
if (__threaded) {
__queue.notify();
try {
__queue.wait();
}
catch (InterruptedException e) {
throw e;
}
}
else {
// We've been asked to add another character to the queue, but it is already
// full and there's
// no other thread to drain it. This should not have happened!
throw new IllegalStateException("Queue is full! Cannot process another character.");
}
}
// Need to do this in case we're not full, but block on a read
if (__readIsWaiting && __threaded) {
__queue.notify();
}
__queue[__queueTail] = ch;
++__bytesAvailable;
if (++__queueTail >= __queue.length) {
__queueTail = 0;
}
}
return bufferWasEmpty;
}
@Override
public int read() throws IOException {
// Critical section because we're altering __bytesAvailable,
// __queueHead, and the contents of _queue in addition to
// testing value of __hasReachedEOF.
synchronized (__queue) {
while (true) {
if (__ioException != null) {
IOException e;
e = __ioException;
__ioException = null;
throw e;
}
if (__bytesAvailable == 0) {
// Return EOF if at end of file
if (__hasReachedEOF) {
return EOF;
}
// Otherwise, we have to wait for queue to get something
if (__threaded) {
__queue.notify();
try {
__readIsWaiting = true;
__queue.wait();
__readIsWaiting = false;
}
catch (InterruptedException e) {
throw new InterruptedIOException("Fatal thread interruption during read.");
}
}
else {
// __alreadyread = false;
__readIsWaiting = true;
int ch;
boolean mayBlock = true; // block on the first read only
do {
try {
if ((ch = __read(mayBlock)) < 0) { // must be EOF
if (ch != WOULD_BLOCK) {
return (ch);
}
}
}
catch (InterruptedIOException e) {
synchronized (__queue) {
__ioException = e;
__queue.notifyAll();
try {
__queue.wait(100);
}
catch (InterruptedException interrupted) {
// Ignored
}
}
return EOF;
}
try {
if (ch != WOULD_BLOCK) {
__processChar(ch);
}
}
catch (InterruptedException e) {
if (__isClosed) {
return EOF;
}
}
// Reads should not block on subsequent iterations. Potentially, this
// could happen if the
// remaining buffered socket data consists entirely of Telnet command
// sequence and no "user" data.
mayBlock = false;
}
// Continue reading as long as there is data available and the queue is not
// full.
while (super.available() > 0 && __bytesAvailable < __queue.length - 1);
__readIsWaiting = false;
}
continue;
}
else {
int ch;
ch = __queue[__queueHead];
if (++__queueHead >= __queue.length) {
__queueHead = 0;
}
--__bytesAvailable;
// Need to explicitly notify() so available() works properly
if (__bytesAvailable == 0 && __threaded) {
__queue.notify();
}
return ch;
}
}
}
}
/***
* Reads the next number of bytes from the stream into an array and returns the number of bytes
* read. Returns -1 if the end of the stream has been reached.
*
*
* @param buffer The byte array in which to store the data.
* @return The number of bytes read. Returns -1 if the end of the message has been reached.
* @exception IOException If an error occurs in reading the underlying stream.
***/
@Override
public int read(byte buffer[]) throws IOException {
return read(buffer, 0, buffer.length);
}
/***
* Reads the next number of bytes from the stream into an array and returns the number of bytes
* read. Returns -1 if the end of the message has been reached. The characters are stored in the
* array starting from the given offset and up to the length specified.
*
*
* @param buffer The byte array in which to store the data.
* @param offset The offset into the array at which to start storing data.
* @param length The number of bytes to read.
* @return The number of bytes read. Returns -1 if the end of the stream has been reached.
* @exception IOException If an error occurs while reading the underlying stream.
***/
@Override
public int read(byte buffer[], int offset, int length) throws IOException {
int ch, off;
if (length < 1) {
return 0;
}
// Critical section because run() may change __bytesAvailable
synchronized (__queue) {
if (length > __bytesAvailable) {
length = __bytesAvailable;
}
}
if ((ch = read()) == EOF) {
return EOF;
}
off = offset;
do {
buffer[offset++] = (byte)ch;
}
while (--length > 0 && (ch = read()) != EOF);
// __client._spyRead(buffer, off, offset - off);
return (offset - off);
}
/*** Returns false. Mark is not supported. ***/
@Override
public boolean markSupported() {
return false;
}
@Override
public int available() throws IOException {
// Critical section because run() may change __bytesAvailable
synchronized (__queue) {
if (__threaded) { // Must not call super.available when running threaded: NET-466
return __bytesAvailable;
}
else {
return __bytesAvailable + super.available();
}
}
}
// Cannot be synchronized. Will cause deadlock if run() is blocked
// in read because BufferedInputStream read() is synchronized.
@Override
public void close() throws IOException {
// Completely disregard the fact thread may still be running.
// We can't afford to block on this close by waiting for
// thread to terminate because few if any JVM's will actually
// interrupt a system read() from the interrupt() method.
super.close();
synchronized (__queue) {
__hasReachedEOF = true;
__isClosed = true;
if (__thread != null && __thread.isAlive()) {
__thread.interrupt();
}
__queue.notifyAll();
}
}
// @Override
public void run() {
int ch;
try {
_outerLoop: while (!__isClosed) {
try {
if ((ch = __read(true)) < 0) {
break;
}
}
catch (InterruptedIOException e) {
synchronized (__queue) {
__ioException = e;
__queue.notifyAll();
try {
__queue.wait(100);
}
catch (InterruptedException interrupted) {
if (__isClosed) {
break _outerLoop;
}
}
continue;
}
}
catch (RuntimeException re) {
// We treat any runtime exceptions as though the
// stream has been closed. We close the
// underlying stream just to be sure.
super.close();
// Breaking the loop has the effect of setting
// the state to closed at the end of the method.
break _outerLoop;
}
// Process new character
boolean notify = false;
try {
notify = __processChar(ch);
}
catch (InterruptedException e) {
if (__isClosed) {
break _outerLoop;
}
}
// Notify input listener if buffer was previously empty
if (notify) {
__client.notifyInputListener();
}
}
}
catch (IOException ioe) {
synchronized (__queue) {
__ioException = ioe;
}
__client.notifyInputListener();
}
synchronized (__queue) {
__isClosed = true; // Possibly redundant
__hasReachedEOF = true;
__queue.notify();
}
__threaded = false;
}
}
/*
* Emacs configuration Local variables: ** mode: java ** c-basic-offset: 4 ** indent-tabs-mode: nil
* ** End: **
*/