From 0b1f5e775236a489efa28f7c2b8ebecd7c05e758 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 2 Jun 2009 01:20:39 +0000 Subject: Refactor relay into separate class git-svn-id: https://connectbot.googlecode.com/svn/trunk/connectbot@257 df292f66-193f-0410-a5fc-6d59da041ff2 --- src/org/connectbot/service/Relay.java | 218 +++++++++++++++++++++++++ src/org/connectbot/service/TerminalBridge.java | 156 +----------------- 2 files changed, 223 insertions(+), 151 deletions(-) create mode 100644 src/org/connectbot/service/Relay.java (limited to 'src') diff --git a/src/org/connectbot/service/Relay.java b/src/org/connectbot/service/Relay.java new file mode 100644 index 0000000..f713210 --- /dev/null +++ b/src/org/connectbot/service/Relay.java @@ -0,0 +1,218 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package org.connectbot.service; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +import android.util.Log; + +import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.Session; + +import de.mud.terminal.vt320; + +/** + * @author Kenny Root + */ +public class Relay implements Runnable { + private static final String TAG = "ConnectBot.Relay"; + + private static final int BUFFER_SIZE = 4096; + + private static final int CONDITIONS = + ChannelCondition.STDOUT_DATA + | ChannelCondition.STDERR_DATA + | ChannelCondition.CLOSED + | ChannelCondition.EOF; + + private TerminalBridge bridge; + + private CharsetDecoder decoder; + private CharsetDecoder replacer; + + private Session session; + + private InputStream stdout; + private InputStream stderr; + + private vt320 buffer; + + private ByteBuffer byteBuffer; + private CharBuffer charBuffer; + + private byte[] byteArray; + private char[] charArray; + + public Relay(TerminalBridge bridge, Session session, InputStream stdout, InputStream stderr, vt320 buffer, String encoding) { + setCharset(encoding); + this.bridge = bridge; + this.session = session; + this.stdout = stdout; + this.stderr = stderr; + this.buffer = buffer; + } + + public void setCharset(String encoding) { + Charset charset = Charset.forName(encoding); + + CharsetDecoder newCd = charset.newDecoder(); + newCd.onUnmappableCharacter(CodingErrorAction.REPLACE); + newCd.onMalformedInput(CodingErrorAction.REPORT); + + CharsetDecoder newReplacer = charset.newDecoder(); + newReplacer.onUnmappableCharacter(CodingErrorAction.REPLACE); + newReplacer.onMalformedInput(CodingErrorAction.REPLACE); + + decoder = newCd; + replacer = newReplacer; + } + + public void run() { + byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); + charBuffer = CharBuffer.allocate(BUFFER_SIZE); + + byteArray = byteBuffer.array(); + charArray = charBuffer.array(); + + int bytesRead = 0; + int offset = 0; + + int newConditions = 0; + + while((newConditions & ChannelCondition.CLOSED) == 0) { + try { + newConditions = session.waitForCondition(CONDITIONS, 0); + if ((newConditions & ChannelCondition.STDOUT_DATA) != 0) { + while (stdout.available() > 0) { + bytesRead = offset + stdout.read(byteArray, offset, BUFFER_SIZE - offset); + + byteBuffer.position(0); + byteBuffer.limit(bytesRead); + + CoderResult coderResult = decoder.decode(byteBuffer, charBuffer, true); + + while (byteBuffer.position() < bytesRead) { + if (coderResult.isMalformed()) + skipMalformedBytes(bytesRead, coderResult); + + coderResult = decoder.decode(byteBuffer, charBuffer, true); + } + + if (coderResult.isMalformed()) + offset = discardMalformedBytes(bytesRead, coderResult); + else { + // No errors at all. + buffer.putString(charArray, 0, charBuffer.position()); + offset = 0; + } + + charBuffer.clear(); + } + + bridge.redraw(); + } + + if ((newConditions & ChannelCondition.STDERR_DATA) != 0) + logAndDiscard(stderr); + + if ((newConditions & ChannelCondition.EOF) != 0) { + // The other side closed our channel, so let's disconnect. + // TODO review whether any tunnel is in use currently. + bridge.dispatchDisconnect(false); + break; + } + } catch (IOException e) { + Log.e(TAG, "Problem while handling incoming data in relay thread", e); + break; + } + } + } + + /** + * @param stream +\ * @throws IOException + */ + private void logAndDiscard(InputStream stream) throws IOException { + while (stream.available() > 0) { + int n = stream.read(byteArray); + byteBuffer.position(0); + byteBuffer.limit(n); + replacer.decode(byteBuffer, charBuffer, false); + // TODO I don't know.. do we want this? We were ignoring it before + Log.d(TAG, String.format("Read data from stream: %s", new String(charArray, 0, charBuffer.position()))); + charBuffer.clear(); + } + } + + /** + * @param n + * @param cr + * @return + */ + private int discardMalformedBytes(int n, CoderResult cr) { + int offset; + /* If we still have malformed input, save the bytes for the next + * read and try to parse it again. + */ + offset = n - byteBuffer.position() + cr.length(); + System.arraycopy(byteArray, byteBuffer.position() - cr.length(), byteArray, 0, offset); + Log.d(TAG, String.format("Copying out %d chars at %d: 0x%02x", + offset, byteBuffer.position() - cr.length(), + byteArray[byteBuffer.position() - cr.length()] + )); + return offset; + } + + /** + * @param numReadBytes + * @param errorResult + */ + private void skipMalformedBytes(int numReadBytes, CoderResult errorResult) { + int curpos = byteBuffer.position() - errorResult.length(); + + if (curpos > 0) { + /* There is good data before the malformed section, so + * pass this on immediately. + */ + buffer.putString(charArray, 0, charBuffer.position()); + } + + byteBuffer.position(curpos); + byteBuffer.limit(curpos + errorResult.length()); + + charBuffer.clear(); + replacer.decode(byteBuffer, charBuffer, true); + + buffer.putString(charArray, 0, charBuffer.position()); + + curpos += errorResult.length(); + + byteBuffer.position(curpos); + byteBuffer.limit(numReadBytes); + + charBuffer.clear(); + } +} diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index 3cf0109..c12eeeb 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -21,12 +21,6 @@ package org.connectbot.service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; -import java.nio.charset.CodingErrorAction; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -62,7 +56,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; -import com.trilead.ssh2.ChannelCondition; import com.trilead.ssh2.Connection; import com.trilead.ssh2.ConnectionInfo; import com.trilead.ssh2.ConnectionMonitor; @@ -92,8 +85,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal public final static String TAG = TerminalBridge.class.toString(); - private final static int BUFFER_SIZE = 4096; - public final static int DEFAULT_FONT_SIZE = 10; public static final String AUTH_PUBLICKEY = "publickey", @@ -123,7 +114,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal private InputStream stderr; - private Thread relay; + private Relay relay; private final String emulation; private final int scrollback; @@ -195,144 +186,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal protected ConnectionInfo connectionInfo; - /** - * @author kenny - * - */ - private final class Relay implements Runnable { - final String encoding = host.getEncoding(); - - public void run() { - final Charset charset = Charset.forName(encoding); - - /* Set up character set decoder to report any byte sequences - * which are malformed so we can try to resume decoding it - * on the next packet received. - * - * UTF-8 byte sequences have a tendency to get truncated at - * times. - */ - final CharsetDecoder cd = charset.newDecoder(); - cd.onUnmappableCharacter(CodingErrorAction.REPLACE); - cd.onMalformedInput(CodingErrorAction.REPORT); - - final CharsetDecoder replacer = charset.newDecoder(); - replacer.onUnmappableCharacter(CodingErrorAction.REPLACE); - replacer.onMalformedInput(CodingErrorAction.REPLACE); - - ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); - CharBuffer cb = CharBuffer.allocate(BUFFER_SIZE); - - final byte[] bba = bb.array(); - final char[] cba = cb.array(); - final byte[] tmpBuff = new byte[BUFFER_SIZE]; - - int n = 0; - int offset = 0; - - int conditions = ChannelCondition.STDOUT_DATA - | ChannelCondition.STDERR_DATA - | ChannelCondition.CLOSED - | ChannelCondition.EOF; - int newConditions = 0; - - while((newConditions & ChannelCondition.CLOSED) == 0) { - try { - newConditions = session.waitForCondition(conditions, 0); - if ((newConditions & ChannelCondition.STDOUT_DATA) != 0) { - while (stdout.available() > 0) { - n = offset + stdout.read(bba, offset, BUFFER_SIZE - offset); - - bb.position(0); - bb.limit(n); - - CoderResult cr = cd.decode(bb, cb, true); - - if (cr.isMalformed()) { - int curpos = bb.position() - cr.length(); - - if (curpos > 0) { - /* There is good data before the malformed section, so - * pass this on immediately. - */ - ((vt320)buffer).putString(cba, 0, cb.position()); - } - - while (bb.position() < n) { - bb.position(curpos); - bb.limit(curpos + cr.length()); - - cb.clear(); - replacer.decode(bb, cb, true); - - ((vt320) buffer).putString(cba, 0, cb.position()); - - curpos += cr.length(); - - bb.position(curpos); - cb.limit(n); - - cb.clear(); - cr = cd.decode(bb, cb, true); - } - - if (cr.isMalformed()) { - /* If we still have malformed input, save the bytes for the next - * read and try to parse it again. - */ - offset = n - bb.position() + cr.length(); - if ((bb.position() - cr.length()) < offset) { - System.arraycopy(bba, bb.position() - cr.length(), tmpBuff, 0, offset); - System.arraycopy(tmpBuff, 0, bba, 0, offset); - } else { - System.arraycopy(bba, bb.position() - cr.length(), bba, 0, offset); - } - Log.d(TAG, String.format("Copying out %d chars at %d: 0x%02x", - offset, bb.position() - cr.length(), - bba[bb.position() - cr.length()] - )); - } else { - // After discarding the previous offset, we only have valid data. - ((vt320)buffer).putString(cba, 0, cb.position()); - offset = 0; - } - } else { - // No errors at all. - ((vt320)buffer).putString(cba, 0, cb.position()); - offset = 0; - } - - cb.clear(); - } - redraw(); - } - - if ((newConditions & ChannelCondition.STDERR_DATA) != 0) { - while (stderr.available() > 0) { - n = stderr.read(bba); - bb.position(0); - bb.limit(n); - replacer.decode(bb, cb, false); - // TODO I don't know.. do we want this? We were ignoring it before - Log.d(TAG, String.format("Read data from stderr: %s", new String(cba, 0, cb.position()))); - cb.clear(); - } - } - - if ((newConditions & ChannelCondition.EOF) != 0) { - // The other side closed our channel, so let's disconnect. - // TODO review whether any tunnel is in use currently. - dispatchDisconnect(false); - break; - } - } catch (IOException e) { - Log.e(TAG, "Problem while handling incoming data in relay thread", e); - break; - } - } - } - } - public class HostKeyVerifier implements ServerHostKeyVerifier { public boolean verifyServerHostKey(String hostname, int port, @@ -821,9 +674,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal stderr = session.getStderr(); // create thread to relay incoming connection data to buffer - relay = new Thread(new Relay()); - relay.setName("Relay"); - relay.start(); + relay = new Relay(this, session, stdout, stderr, (vt320) buffer, host.getEncoding()); + Thread relayThread = new Thread(relay); + relayThread.setName("Relay"); + relayThread.start(); // force font-size to make sure we resizePTY as needed setFontSize(fontSize); -- cgit v1.2.3