package jcifs.util.transport; import java.io.*; import java.net.*; import java.util.*; import jcifs.util.LogStream; /** * This class simplifies communication for protocols that support * multiplexing requests. It encapsulates a stream and some protocol * knowledge (provided by a concrete subclass) so that connecting, * disconnecting, sending, and receiving can be syncronized * properly. Apparatus is provided to send and receive requests * concurrently. */ public abstract class Transport implements Runnable { static int id = 0; static LogStream log = LogStream.getInstance(); public static int readn( InputStream in, byte[] b, int off, int len ) throws IOException { int i = 0, n = -5; while (i < len) { n = in.read( b, off + i, len - i ); if (n <= 0) { break; } i += n; } return i; } /* state values * 0 - not connected * 1 - connecting * 2 - run connected * 3 - connected * 4 - error */ int state = 0; String name = "Transport" + id++; Thread thread; TransportException te; protected HashMap response_map = new HashMap( 4 ); protected abstract void makeKey( Request request ) throws IOException; protected abstract Request peekKey() throws IOException; protected abstract void doSend( Request request ) throws IOException; protected abstract void doRecv( Response response ) throws IOException; protected abstract void doSkip() throws IOException; public synchronized void sendrecv( Request request, Response response, long timeout ) throws IOException { makeKey( request ); response.isReceived = false; try { response_map.put( request, response ); doSend( request ); response.expiration = System.currentTimeMillis() + timeout; while (!response.isReceived) { wait( timeout ); timeout = response.expiration - System.currentTimeMillis(); if (timeout <= 0) { throw new TransportException( name + " timedout waiting for response to " + request ); } } } catch( IOException ioe ) { if (log.level > 2) ioe.printStackTrace( log ); try { disconnect( true ); } catch( IOException ioe2 ) { ioe2.printStackTrace( log ); } throw ioe; } catch( InterruptedException ie ) { throw new TransportException( ie ); } finally { response_map.remove( request ); } } private void loop() { while( thread == Thread.currentThread() ) { try { Request key = peekKey(); if (key == null) throw new IOException( "end of stream" ); synchronized (this) { Response response = (Response)response_map.get( key ); if (response == null) { if (log.level >= 4) log.println( "Invalid key, skipping message" ); doSkip(); } else { doRecv( response ); response.isReceived = true; notifyAll(); } } } catch( Exception ex ) { String msg = ex.getMessage(); boolean timeout = msg != null && msg.equals( "Read timed out" ); /* If just a timeout, try to disconnect gracefully */ boolean hard = timeout == false; if (!timeout && log.level >= 3) ex.printStackTrace( log ); try { disconnect( hard ); } catch( IOException ioe ) { ioe.printStackTrace( log ); } } } } /* Build a connection. Only one thread will ever call this method at * any one time. If this method throws an exception or the connect timeout * expires an encapsulating TransportException will be thrown from connect * and the transport will be in error. */ protected abstract void doConnect() throws Exception; /* Tear down a connection. If the hard parameter is true, the diconnection * procedure should not initiate or wait for any outstanding requests on * this transport. */ protected abstract void doDisconnect( boolean hard ) throws IOException; public synchronized void connect( long timeout ) throws TransportException { try { switch (state) { case 0: break; case 3: return; // already connected case 4: state = 0; throw new TransportException( "Connection in error", te ); default: TransportException te = new TransportException( "Invalid state: " + state ); state = 0; throw te; } state = 1; te = null; thread = new Thread( this, name ); thread.setDaemon( true ); synchronized (thread) { thread.start(); thread.wait( timeout ); /* wait for doConnect */ switch (state) { case 1: /* doConnect never returned */ state = 0; thread = null; throw new TransportException( "Connection timeout" ); case 2: if (te != null) { /* doConnect throw Exception */ state = 4; /* error */ thread = null; throw te; } state = 3; /* Success! */ return; } } } catch( InterruptedException ie ) { state = 0; thread = null; throw new TransportException( ie ); } finally { /* This guarantees that we leave in a valid state */ if (state != 0 && state != 3 && state != 4) { if (log.level >= 1) log.println("Invalid state: " + state); state = 0; thread = null; } } } public synchronized void disconnect( boolean hard ) throws IOException { IOException ioe = null; switch (state) { case 0: /* not connected - just return */ return; case 2: hard = true; case 3: /* connected - go ahead and disconnect */ if (response_map.size() != 0 && !hard) { break; /* outstanding requests */ } try { doDisconnect( hard ); } catch (IOException ioe0) { ioe = ioe0; } case 4: /* in error - reset the transport */ thread = null; state = 0; break; default: if (log.level >= 1) log.println("Invalid state: " + state); thread = null; state = 0; break; } if (ioe != null) throw ioe; } public void run() { Thread run_thread = Thread.currentThread(); Exception ex0 = null; try { /* We cannot synchronize (run_thread) here or the caller's * thread.wait( timeout ) cannot reaquire the lock and * return which would render the timeout effectively useless. */ doConnect(); } catch( Exception ex ) { ex0 = ex; // Defer to below where we're locked return; } finally { synchronized (run_thread) { if (run_thread != thread) { /* Thread no longer the one setup for this transport -- * doConnect returned too late, just ignore. */ if (ex0 != null) { if (log.level >= 2) ex0.printStackTrace(log); } return; } if (ex0 != null) { te = new TransportException( ex0 ); } state = 2; // run connected run_thread.notify(); } } /* Proccess responses */ loop(); } public String toString() { return name; } }