/* This file is part of "MidpSSH". * Copyright (c) 2004 Karl von Randow. * * MidpSSH is based upon Telnet Floyd and FloydSSH by Radek Polak. * * --LICENSE NOTICE-- * 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 2 * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * --LICENSE NOTICE-- * */ package app.session; import gui.Activatable; import gui.MainMenu; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Random; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.io.StreamConnection; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.AlertType; import terminal.Terminal; import terminal.VT320; import app.SessionSpec; import app.Settings; /** * @author Karl von Randow * */ public abstract class Session implements Activatable { /** * After this count of millisends without communication on connection we will * send something just to keep connection alive */ public static final int keepAliveTime = 1000 * 60 * 1; // 1 minute protected VT320 emulation; protected SessionIOHandler filter; private Terminal terminal; private boolean disconnecting, erroredDisconnect; private boolean forcedDisconnect; private boolean pollingIO; /** * Holds the socket connetion object (from the Generic Connection Framework) * that is the basis of this connection. */ private StreamConnection connection; /** * Holds the InputStream associated with the socket. */ private InputStream in; /** * Holds the OutputStream associated with the socket. */ private OutputStream out; private SessionSpec spec; //#ifdef readwriteio private Thread readWriter; //#else private Thread reader, writer; //#endif /** * We will collect here data for writing. Data will be sent when nothing is * in input stream, otherwise midlet hung up. * * @see #run */ private byte[] outputBuffer = new byte[16]; // this will grow if needed private Object writerMutex = new Object(); /** * Number of bytes to be written, from output array, because it has fixed * lenght. */ private int outputCount = 0; private int bytesWritten = 0, bytesRead = 0; public Session() { emulation = new VT320() { public void sendData( byte[] b, int offset, int length ) throws IOException { filter.handleSendData( b, offset, length ); } //#ifdef midp2 public void beep() { MainMenu.getDisplay().vibrate(200); } //#endif }; pollingIO = Settings.pollingIO; terminal = new Terminal( emulation, this ); //#ifdef readwriteio readWriter = new ReadWriter(); //#else reader = new Reader(); writer = new Writer(); //#endif } protected void connect( SessionSpec spec, SessionIOHandler filter ) { this.spec = spec; this.filter = filter; //#ifdef readwriteio readWriter.start(); //#else writer.start(); //#endif } protected abstract int defaultPort(); /* * (non-Javadoc) * * @see telnet.TelnetIOListener#receiveData(byte[]) */ protected void receiveData( byte[] buffer, int offset, int length ) throws IOException { if ( buffer != null && length > 0 ) { try { emulation.putString( new String( buffer, offset, length ) ); } catch ( Exception e ) { } } } /* * (non-Javadoc) * * @see telnet.TelnetIOListener#sendData(byte[]) */ protected void sendData( byte[] b, int offset, int length ) throws IOException { //#ifndef noiosync synchronized ( writerMutex ) { //#endif if ( outputCount + length > outputBuffer.length ) { byte[] newOutput = new byte[outputCount + length]; System.arraycopy( outputBuffer, 0, newOutput, 0, outputCount ); outputBuffer = newOutput; } System.arraycopy( b, offset, outputBuffer, outputCount, length ); outputCount += length; //#ifndef noiosync writerMutex.notify(); } //#endif } public void typeString( String str ) { emulation.stringTyped( str ); } public void typeChar( char c, int modifiers ) { emulation.keyTyped( 0, c, modifiers ); } public void typeKey( int keyCode, int modifiers ) { emulation.keyPressed( keyCode, modifiers ); } private boolean connect() throws IOException { String host = spec.host; emulation.putString( "Connecting to " + host + "..." ); if ( host.indexOf( ':' ) == -1 ) { host += ":" + defaultPort(); } StringBuffer conn; String httpProxy = Settings.httpProxy; if (httpProxy.length() == 0 || Settings.httpProxyMode == 0) { conn = new StringBuffer("socket://").append(host); //#ifdef blackberry customiseConnectionString(conn); //#endif connection = (StreamConnection) Connector.open( conn.toString(), Connector.READ_WRITE, false ); in = connection.openDataInputStream(); out = connection.openDataOutputStream(); } else { /* Connect using HTTP proxy */ emulation.putString("\r\nUsing HTTP Proxy..."); int id = new Random().nextInt(); conn = new StringBuffer("http://").append(httpProxy).append('/').append(id).append('/').append(host); //#ifdef blackberry customiseConnectionString(conn); //#endif if (Settings.httpProxyMode == 1) { HttpConnection outbound = (HttpConnection) Connector.open(conn.toString(), Connector.READ_WRITE, false); outbound.setRequestMethod(HttpConnection.POST); out = outbound.openOutputStream(); HttpConnection inbound = (HttpConnection) Connector.open(conn.toString(), Connector.READ_WRITE, false); inbound.setRequestProperty("X-MidpSSH-Persistent", "true"); in = inbound.openInputStream(); } else { out = new HttpOutboundStream(conn.toString()); in = new HttpInboundStream(conn.toString()); } } emulation.putString( "OK\r\n" ); return true; } //#ifdef blackberry private void customiseConnectionString(StringBuffer conn) { //#ifdef blackberryconntypes if ( spec.blackberryConnType == SessionSpec.BLACKBERRY_CONN_TYPE_PROXY ) { conn.append(";deviceside=false"); } else if ( spec.blackberryConnType == SessionSpec.BLACKBERRY_CONN_TYPE_DEVICESIDE ) { conn.append(";deviceside=true"); } else if ( spec.blackberryConnType == SessionSpec.BLACKBERRY_CONN_TYPE_WIFI ) { conn.append(";DeviceSide=True;ConnectionUID=S TCP-WiFi;ConnectionSetup=delayed;retrynocontext=true"); } //#endif //#ifdef blackberryenterprise conn.append(";deviceside=false"); //#endif } //#endif //#ifdef readwriteio private void readWrite() throws IOException { byte [] buf = new byte[512]; while (!disconnecting) { boolean doneSomething = false; /* Check if there is any data to read */ int a = in.available(); if (a > 0) { /* Read only as much data as is available */ int n = in.read( buf, 0, Math.min( a, buf.length ) ); bytesRead += n; filter.handleReceiveData( buf, 0, n ); doneSomething = true; } /* Check if there is any data to write */ synchronized (writerMutex) { if (outputCount > 0) { bytesWritten += outputCount; out.write( outputBuffer, 0, outputCount ); out.flush(); outputCount = 0; doneSomething = true; } } if (!doneSomething) { try { Thread.sleep(100); } catch (InterruptedException e) { } } } } //#else /** * Continuously read from remote host and display the data on screen. */ private void read() throws IOException { byte [] buf; //#ifndef slowreadio buf = new byte[512]; // try a smaller buffer, maybe works better on some phones int n = 0; while ( n != -1 ) { bytesRead += n; filter.handleReceiveData( buf, 0, n ); //#ifndef io.no_available int a = in.available(); if (pollingIO) { while (a == 0 && !disconnecting) { try { Thread.sleep(100); } catch (InterruptedException e) { } a = in.available(); } } // Read at least 1 byte, and at most the number of bytes available n = in.read( buf, 0, Math.max( 1, Math.min( a, buf.length ) ) ); //#else n = in.read(buf, 0, buf.length); //#endif } //#else buf = new byte[1]; //#ifdef debugging emulation.putString( "Waiting for first byte.\r\n" ); //#endif int c = in.read(); //#ifdef debugging emulation.putString( "Read first byte.\r\n" ); //#endif while ( c != -1 ) { bytesRead++; buf[0] = (byte) ( c & 0xff ); filter.handleReceiveData( buf, 0, 1 ); c = in.read(); } //#endif } private void write() throws IOException { final byte [] empty = new byte[0]; while ( !disconnecting ) { //#ifndef noiosync synchronized ( writerMutex ) { //#else int sleepCount = 0; //#endif while ( outputCount == 0 && !disconnecting ) { try { //#ifndef noiosync writerMutex.wait( keepAliveTime ); //#else Thread.sleep(100); if (sleepCount++ < 600) { continue; // to avoid the keep-alive send below } else { sleepCount = 0; // and go ahead and send keep-alive } //#endif } catch ( InterruptedException e ) { } if ( outputCount == 0 && !disconnecting ) { // No data to send after timeout so send an empty array through the filter which will trigger the // sending of a NOOP (see TelnetSession and SshSession) - this has the effect of a keepalive //emulation.putString( "NOOP\r\n" ); filter.handleSendData( empty, 0, 0 ); } } if ( !disconnecting ) { //#ifdef noiosync /* If we aren't syncing I/O then we should make a copy of relevant data structures so that * we don't stumble if they are changed while we're sending, this is quite likely to happen * as sending can be a slow operation. */ byte [] outputBuffer = new byte[ outputCount ]; int outputCount = this.outputCount; this.outputCount = 0; System.arraycopy( this.outputBuffer, 0, outputBuffer, 0, outputCount ); //#endif bytesWritten += outputCount; out.write( outputBuffer, 0, outputCount ); out.flush(); outputCount = 0; } //#ifndef noiosync } //#endif } } //#endif private void handleException( String where, Throwable t ) { if ( !disconnecting ) { Alert alert = new Alert( "Session Error" ); alert.setType( AlertType.ERROR ); alert.setTimeout(Alert.FOREVER); String msg = t.getMessage(); if ( msg == null ) msg = t.toString(); alert.setString( where + ": " + msg ); MainMenu.setDisplay( alert ); } } /** * @return Returns the terminal. */ public Terminal getTerminal() { return terminal; } /* * (non-Javadoc) * * @see telnet.Session#disconnect() */ public void disconnect() { forcedDisconnect = true; doDisconnect(); } private void doDisconnect() { if ( !disconnecting ) { //#ifndef noiosync synchronized ( writerMutex ) { //#endif disconnecting = true; try { if ( in != null ) in.close(); if ( out != null ) out.close(); if ( connection != null ) connection.close(); } catch ( IOException e ) { handleException( "Disconnect", e ); } //#ifndef noiosync writerMutex.notify(); } //#endif } } private String bytesToString( int bytes ) { if ( bytes < 1024 ) { return bytes + " bytes"; } else if ( bytes < 1024 * 1024 ) { return to2dp( bytes * 100 / 1024 ) + " KB"; } else { return to2dp( bytes * 100 / ( 1024 * 1024 ) ) + " MB"; } } private String to2dp( int i ) { String str = "" + i; return str.substring( 0, str.length() - 2 ) + "." + str.substring( str.length() - 2 ); } private void sessionReport() { if ( !erroredDisconnect ) { String report = "IN: " + bytesToString( bytesRead ) + "\nOUT: " + bytesToString( bytesWritten ) + "\nTOTAL: " + bytesToString( bytesRead + bytesWritten ); Alert alert = new Alert( "Session Report" ); alert.setType( AlertType.INFO ); alert.setString( report ); alert.setTimeout( Alert.FOREVER ); if ( forcedDisconnect ) { MainMenu.alertBackToMain( alert ); } else { MainMenu.alert( alert, terminal ); } } } public void goMainMenu() { MainMenu.goMainMenu(); } /* * (non-Javadoc) * * @see gui.Activatable#activate() */ public void activate() { terminal.activate(); } public void activate( Activatable back ) { activate(); } //#ifdef readwriteio private class ReadWriter extends Thread { public void run() { try { connect(); terminal.connected(); readWrite(); doDisconnect(); terminal.disconnected(); sessionReport(); } catch ( Exception e ) { handleException( "ReadWriter", e ); erroredDisconnect = true; doDisconnect(); terminal.disconnected(); } } } //#else private class Reader extends Thread { public void run() { try { //#ifdef debugging emulation.putString( "Reader started.\r\n" ); //#endif read(); doDisconnect(); } catch ( Exception e ) { handleException( "Reader", e ); erroredDisconnect = true; doDisconnect(); } } } private class Writer extends Thread { public void run() { try { connect(); terminal.connected(); reader.start(); write(); doDisconnect(); terminal.disconnected(); sessionReport(); } catch ( Exception e ) { handleException( "Writer", e ); erroredDisconnect = true; doDisconnect(); terminal.disconnected(); } } } //#endif }