/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

/*
 * Created on Aug 13, 2005
 */
 
package org.jboss.test.remoting.transport.multiplex;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import javax.net.SocketFactory;

import org.jboss.remoting.transport.multiplex.MasterServerSocket;
import org.jboss.remoting.transport.multiplex.MultiplexingManager;
import org.jboss.remoting.transport.multiplex.SocketId;
import org.jboss.remoting.transport.multiplex.VirtualSocket;
import org.jboss.remoting.transport.multiplex.VirtualSocketFactory;


/**
 * A BasicBehaviorClient.
 
 * @author <a href="mailto:r.sigal@computer.org">Ron Sigal</a>
 * @version $Revision: 996 $
 * <p>
 * Copyright (c) 2005
 * </p>
 */

public class BasicSocketBehaviorClient extends ShutdownClient implements MultiplexConstants
{  
   public void testSocketIds() throws Exception
   {
      try
      {
         byte[] script = new byte[] {CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }

      SocketId sid = new SocketId();
      int port = sid.getPort();
      int maxPort = port + 10;
      SocketId.setMaxPort(maxPort);
      
      for (int i = port + 1; i <= maxPort; i++)
         new SocketId().releasePort();
      
      sid = new SocketId();
      sid.releasePort();
      assertTrue(sid.getPort() == port + 1);
      
      for (int i = port + 2; i <= maxPort; i++)
         new SocketId().releasePort();
      
      sid = new SocketId();
      assertTrue(sid.getPort() == port + 1);
      
      for (int i = port + 2; i <= maxPort; i++)
         new SocketId();
      
      class TestThread extends Thread
      {
         SocketId s;
         SocketId getS() {return s;}
         public void run()
         {
            s = new SocketId();
         }
      };
      
      TestThread t = new TestThread();
      t.start();
      assertNull(t.getS());
      sid.releasePort();
      Thread.sleep(1000);
      assertTrue(t.getS().getPort() == port + 1);
      SocketId.setMaxPort(Integer.MAX_VALUE - 1);
      
      log.info("testSocketIds() PASSES");
   }
   
   /**
    * If underlying InputStream gets EOF, virtual InputStream should indicate EOF.
    *
    */
   public void testSocketEOF()
   {
      log.info("entering testSocketEOF()");
      
      try
      {
         byte[] script = new byte[] {CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }  
      
      class ServerThread extends Thread
      {
         public void run()
         {
            try
            {
               MasterServerSocket ss = new MasterServerSocket(5343);
               VirtualSocket s = (VirtualSocket) ss.accept();
               Thread.sleep(2000);
               s.getMultiplexingManager().getSocket().close();
               s.close();
               ss.close();
            }
            catch (Exception e)
            {
               log.error(e);
            }  
         }
      }
      
      try
      {
         new ServerThread().start();
         Thread.sleep(1000);
         VirtualSocket s = new VirtualSocket("localhost", 5343);
         is = s.getInputStream();
         assertTrue(is.read() == -1);
         s.close();
      }
      catch (Exception e)
      {
         log.error(e);
      }
      
      log.info("testSocketEOF() PASSES");
   }
   
   
   public void testDisconnectCallbacks()
   {
      class LocalDisconnectListener implements VirtualSocket.DisconnectListener
      {
         private boolean notified;
         private VirtualSocket vs;
         public void notifyDisconnected(VirtualSocket vs) {this.vs = vs; notified = true;}
         public VirtualSocket getVirtualSocket() {return vs;}
         public boolean isNotified() {return notified;}
      }
      
      log.info("entering testDisconnectCallbacks()");
      
      try
      {
         byte[] script = new byte[] {READ, SHUTDOWN_OUTPUT, READ, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }

      try
      {
         LocalDisconnectListener listener = new LocalDisconnectListener();
         ((VirtualSocket) testSocket).addDisconnectListener(listener);
         assertFalse(listener.isNotified());
         os = testSocket.getOutputStream();
         
         // Tell server to shut down output.
         os.write(3);
         assertFalse(listener.isNotified());
         
         // Tell server to close socket.
         os.write(5);
         
         for (int i = 0; i < 3; i++)
         {
            Thread.sleep((i + 1) * 2000);
            
            if (listener.isNotified())
               break;
         }
         
         assertTrue(listener.isNotified());
         assertTrue(testSocket == listener.getVirtualSocket());
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }

      log.info("testDisconnectCallbacks() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 *
 */
   public void testVirtualSocketConstructors()
   {
      log.info("entering testVirtualSocketConstructors()");
      
      try
      {
         // This script is for server thread created for testSocket, 
         // which we don't use in this test.
         byte[] script = new byte[] {CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }
      
      try
      {         
         while (true)
         {
            try
            {
               int localPort = random.nextInt() & 0xffff;
               VirtualSocket socket1 = new VirtualSocket();
               socket1.bind(new InetSocketAddress(localPort));
               socket1.connect(basicBehaviorServerSocketAddress);
               assertTrue(doOneConstructorTest(socket1, localPort));
               break;
            }
            catch (BindException e)
            {
               log.info("looking for a free socket");
            }
         }
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         VirtualSocket socket2 = new VirtualSocket(basicBehaviorServerHost , basicBehaviorServerPort);
         assertTrue(doOneConstructorTest(socket2, socket2.getLocalPort()));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
           
      try
      {
         VirtualSocket socket3 = new VirtualSocket(basicBehaviorServerHost , basicBehaviorServerPort, false);
         fail();
      }
      catch (SocketException e)
      {
         assertTrue(e.getMessage().equals("Deprecated: use DataGramSocket instead of stream = false"));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
           
      try
      {
         VirtualSocket socket4 = new VirtualSocket(basicBehaviorServerHost , basicBehaviorServerPort, true);
         assertTrue(doOneConstructorTest(socket4, socket4.getLocalPort()));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
     
      try
      {
         InetAddress inetAddress = InetAddress.getByName(basicBehaviorServerHost);
         VirtualSocket socket5 = new VirtualSocket(inetAddress , basicBehaviorServerPort);
         assertTrue(doOneConstructorTest(socket5, socket5.getLocalPort()));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         InetAddress inetAddress = InetAddress.getByName(basicBehaviorServerHost);
         VirtualSocket socket6 = new VirtualSocket(inetAddress , basicBehaviorServerPort, false);
         fail();
      }
      catch (SocketException e)
      {
         assertTrue(e.getMessage().equals("Deprecated: use DataGramSocket instead of stream = false"));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         InetAddress inetAddress = InetAddress.getByName(basicBehaviorServerHost);
         VirtualSocket socket7 = new VirtualSocket(inetAddress , basicBehaviorServerPort, true);
         assertTrue(doOneConstructorTest(socket7, socket7.getLocalPort()));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         while (true)
         {
            try
            {
               int localPort = random.nextInt() & 0xffff;
               InetAddress inetAddress = InetAddress.getByName("localhost");
               VirtualSocket socket8 = new VirtualSocket(basicBehaviorServerHost, basicBehaviorServerPort, inetAddress , localPort);
               assertTrue(doOneConstructorTest(socket8, localPort));
               break;
            }
            catch (BindException e)
            {
               log.info("looking for a free socket");
            }
         }
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {         
         while (true)
         {
            try
            {
               int localPort = random.nextInt() & 0xffff;
               InetAddress remoteAddress = InetAddress.getByName(basicBehaviorServerHost);
               InetAddress localAddress = InetAddress.getByName("localhost");
               VirtualSocket socket9 = new VirtualSocket(remoteAddress, basicBehaviorServerPort, localAddress , localPort);
               assertTrue(doOneConstructorTest(socket9, localPort));
               break;
            }
            catch (BindException e)
            {
               log.info("looking for a free socket");
            }
         }
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      log.info("testVirtualSocketConstructors() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 *
 */
   protected boolean doOneConstructorTest(VirtualSocket socket, int localPort)
   {
      boolean success = true;
      
      try
      {
         byte[] script = new byte[] {READ, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
         
      }
      catch (IOException e1)
      {
         e1.printStackTrace();
         log.error(e1);
      }
      
      assertTrue(socket.isBound());
      assertTrue(socket.isConnected());
      assertTrue(socket.getLocalPort() == localPort);
      
      assertTrue(socket.getPort() == basicBehaviorServerPort);
      
      try
      {
         InetAddress localAddress = InetAddress.getByName("localhost");
         InetSocketAddress localSocketAddress = new InetSocketAddress(localAddress, localPort);
         InetAddress ia = socket.getLocalAddress();
         assertTrue(socket.getLocalAddress().equals(localAddress));
         assertTrue(socket.getLocalSocketAddress().equals(localSocketAddress));
      }
      catch (UnknownHostException e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         InetAddress remoteAddress = InetAddress.getByName(basicBehaviorServerHost);
         assertTrue(socket.getInetAddress().equals(remoteAddress));
         assertTrue(socket.getRemoteSocketAddress().equals(basicBehaviorServerSocketAddress));
      }
      catch (UnknownHostException e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         // end script on server
         os = socket.getOutputStream();
         os.write(3);
      }
      catch (IOException e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         socket.close();
      }
      catch (IOException e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }

      return success;
   }
   
   
/**
 *  FIXME Comment this
 * 
 *
 */
   public void testArrayRead()
   {
      log.info("entering testArrayRead()");
      
      try
      {
         byte[] script = new byte[]
         {
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            CLOSE_TEST_SOCKET
         }
         ;
         scriptStream.write(script.length);
         scriptStream.write(script);  
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         
         os.write(3);
         os.write(5);
         os.write(7);
         os.write(9);
         os.write(11);
         
         while(is.available() < 3)
            try
            {
               Thread.sleep(500);
            }
         	catch (InterruptedException e) {}
         	
         byte[] bytes = new byte[3];
         int n = is.read(bytes, 0, 3);
         log.info("n = " + n);
         assertTrue(n == 3);
         assertTrue(bytes[0] == 3);
         assertTrue(bytes[1] == 5);
         assertTrue(bytes[2] == 7);
         
         while(is.available() < 2)
            try
            {
               Thread.sleep(500);
            }
            catch (InterruptedException e) {}
         
         n = is.read(bytes, 1, 2);
         log.info("n = " + n);
         assertTrue(n == 2);
         assertTrue(bytes[1] == 9);
         assertTrue(bytes[2] == 11);
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testArrayRead() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 *
 */
   public void testSkip()
   {
      log.info("entering testSkip()");
      
      try
      {
         byte[] script = new byte[]
         {
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            WRITE,
            READ,
            CLOSE_TEST_SOCKET
         };
         
         scriptStream.write(script.length);
         scriptStream.write(script);  
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         
         assertTrue(is.skip(-1) == 0);
         log.info("skip 1");
         
         os.write(3);
         os.write(5);
         os.write(7);
         os.write(9);
         
         assertTrue(is.read() == 3);
         int k = (int) is.skip(2);
         
         if (k == 1)
            is.skip(1);
         
         log.info("skip 2");
         assertTrue(is.read() == 9);
         
         os.write(11);
         os.write(13);
         os.write(15);
         os.write(17);
         
         assertTrue(is.read() == 11);
         k = (int) is.skip(2);
         
         if (k == 1)
            is.skip(1);
         
         log.info("skip 3");
         assertTrue(is.read() == 17);
         
         try
         {
            testSocket.setSoTimeout(1000);
            log.info("" + is.skip(1));
            fail();
         }
         catch (Exception e)
         {
            log.info("skip 4");
            assertTrue(e instanceof SocketTimeoutException && e.getMessage().equals("Read timed out"));
         }
         
         os.write(19);
         
         // Give remote socket a chance to close.
         try
         {
            Thread.sleep(2000);
         }
         catch (InterruptedException ignored) {}
         
         assertTrue(is.skip(1) == 0);
         log.info("skip 5");
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testSkip() PASSES");
   }
   
   
   /**
    *  FIXME Comment this
    * 
    * @throws Exception
    */
   public void testLocalInputShutdown() throws Exception
   {
      log.info("entering testLocalInputShutdown()");
      
      try
      {
         byte[] script = new byte[] {READ, WRITE, READ, READ, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);  
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(3);
         int b = is.read();
         assertTrue(b == 3);
         testSocket.shutdownInput();
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
         b = is.read();
         assertTrue(b == -1);
         os.write(5);
         os = testSocket.getOutputStream();
         os.write(7);
         testSocket.shutdownOutput();
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      try
      {
         testSocket.getInputStream();
         fail();
      }
      catch (SocketException e)
      {
         assertTrue(e.getMessage().equals("Socket input is shutdown"));
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      try
      {
         testSocket.shutdownInput();
         fail();
      }
      catch (SocketException e)
      {
         assertTrue(e.getMessage().equals("Socket input is already shutdown"));
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testLocalInputShutdown() PASSES");
   }
   
   public void testLocalOutputShutdown() throws Exception
   {
      log.info("entering testLocalOutputShutdown()");
      
      try
      {
         byte[] script = new byte[] {READ, WRITE, READ, WRITE, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);  
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(3);
         os.write(5);
         int b = is.read();
         assertTrue(b == 3);
         testSocket.shutdownOutput();
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue( testSocket.isOutputShutdown());
         is = testSocket.getInputStream();
         b = is.read();
         assertTrue(b == 5);
         testSocket.shutdownInput();
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         // Note.  Real sockets allow the first write to succeed.  Is this important?
         os.write(5);
         log.error("write() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Broken pipe"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.getInputStream();
         log.error("getInputStream() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket input is shutdown"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.shutdownOutput();
         log.error("shutdownOutput() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket output is already shutdown"));
      }
      
      log.info("testLocalOutputShutdown() PASSES");
   }
   
   
   class LocalSocketCloseThread extends Thread
   {
      Socket socket;
      boolean success = false;
      boolean done = false;
      LocalSocketCloseThread(Socket socket) {this.socket = socket;}
      boolean getSuccess() {return success;}
      void setDone() {done = true;}
      public void run()
      {
         synchronized (this)
         {
            notifyAll();
         }
         
         try
         {
            InputStream is = socket.getInputStream();
            log.info("" + is.read());
         }
         catch (IOException e)
         {
            log.debug(e);
            success = e.getMessage().equals("Socket closed");
         }
  
         while (!done)
         {
            try
            {
               Thread.sleep(500);
            }
            catch (InterruptedException ignored) {}
            
            synchronized (this)
            {
               notifyAll();
            }
         }
      }
   }
   /**
    *  FIXME Comment this
    * 
    * @throws Exception
    */
   public void testLocalSocketClose() throws Exception
   {
      log.info("entering testLocalSocketClose()");
      
      try
      {
         byte[] script = new byte[] {READ, WRITE, SLEEP, 0x10, 0x00, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script); 
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(3);
         assertTrue((is.read() == 3));
         LocalSocketCloseThread t = new LocalSocketCloseThread(testSocket);
         t.start();
         Thread.sleep(2000);
         testSocket.close();

         synchronized (t)
         {
            t.wait(4000);
         }

         t.setDone();         
         assertTrue(t.getSuccess());
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         int b = is.read();
         // read() should have failed
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         // Note.  Real sockets allow the first write to succeed.  Is this important?
         os.write(7);
         log.error("write() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.getInputStream();
         log.error("getInputStream() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket is closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.getOutputStream();
         log.error("getOutputStream() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket is closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.shutdownInput();
         log.error("shutdownInput() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket is closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      try
      {
         testSocket.shutdownOutput();
         log.error("shutdownOutput() should have failed");
         fail();
      }
      catch (SocketException e)
      {
         log.debug(e);
         assertTrue(e.getMessage().equals("Socket is closed"));
      }
      catch (Exception e)
      {
         e.printStackTrace();
         log.error(e);
         fail();
      }
      
      log.info("testLocalSocketClose() PASSES");
   }
   
   /**
    *  FIXME Comment this
    * 
    * @throws Exception
    */
   public void testRemoteInputShutdown() throws Exception
   {
      log.info("entering testRemoteInputShutdown()");
      
      try
      {
         byte[] script = new byte[]
         {
               READ,
               WRITE,
               READ,
               SHUTDOWN_INPUT,
               WRITE,
               SLEEP, 0x08, 0x00, // sleep 2048 milliseconds
               CLOSE_TEST_SOCKET
         };
         scriptStream.write(script.length);
         scriptStream.write(script); 
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(3);
         int b = is.read();
         assertTrue(b == 3);
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
         os.write(5);
         // remote socket will shut down input after reading 5
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());  
         b = is.read();
         assertTrue(b == 5);
         os.write(7);
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(11);
         testSocket.shutdownInput();
         testSocket.shutdownOutput();
         testSocket.close();
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testRemoteInputShutdown() PASSES");
   }
   
   
   /**
    *  FIXME Comment this
    * 
    * @throws Exception
    */
   public void testRemoteOutputShutdown() throws Exception
   {
      log.info("entering testRemoteOutputShutdown()");
      
      try
      {
         byte[] script = new byte[] {READ, WRITE, READ, WRITE, READ, SHUTDOWN_OUTPUT, READ, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script); 
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         os.write(3);
         int b = is.read();
         assertTrue(b == 3);
         os.write(5);
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
         os.write(7);
         Thread.sleep(5000);
         // remote socket will shut down output after reading 7
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());  
         b = is.read();
         assertTrue(b == 5);
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         b = is.read();
         assertTrue(b == -1);
         os.write(9);
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testRemoteOutputShutdown() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 * @throws Exception
 */
   public void testRemoteSocketClose() throws Exception
   {
      log.info("entering testRemoteSocketClose()");
      
      try
      {
         byte[] script = new byte[] {READ, WRITE, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script); 
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
         os.write(3);
         // give remote socket time to close
         Thread.sleep(4000);
         int b = is.read();
         assertTrue(b == 3);
         assertTrue(testSocket.isConnected());
         assertTrue(testSocket.isBound());
         assertTrue(!testSocket.isClosed());
         assertTrue(!testSocket.isInputShutdown());
         assertTrue(!testSocket.isOutputShutdown());
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         b = is.read();
         assertTrue(b == -1);
      }
      catch (Throwable t)
      {
         log.error(t);
         t.printStackTrace();
         fail();
      }
      
      try
      {
         // remote socket should be closed by now, but just in case ...
         Thread.sleep(1000);
         os.write(5);
         
         try
         {
            os.write(7);
            fail();
         }
         catch (SocketException e)
         {
            assertTrue(e.getMessage().equals("Broken pipe"));
         }
        
         try
         {
            os.write(11);
            fail();
         }
         catch (SocketException e)
         {
            assertTrue(e.getMessage().equals("Broken pipe"));
         }
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         log.error(t);
         fail();
      }
      
      log.info("testRemoteSocketClose() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 *
 */
   public void testSocketFactory()
   {
      log.info("entering testSocketFactory()");
      
      try
      {
         // This script is for server thread created for testSocket, 
         // which we don't use in this test.
         byte[] script = new byte[] {CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }
      
      SocketFactory socketFactory = VirtualSocketFactory.getDefault();
      Socket socket = null;
      
      try
      {
         socket = socketFactory.createSocket();
         socket.connect(basicBehaviorServerSocketAddress);
         assertTrue(doOneSocketFactoryTest(socket));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      
      try
      {
         socket = socketFactory.createSocket(basicBehaviorServerHost, basicBehaviorServerPort);
         assertTrue(doOneSocketFactoryTest(socket));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         InetAddress inetAddress = InetAddress.getByName("localhost");
         int localPort = random.nextInt() & 0xffff;
         
         while (true)
         {
            try
            {
               socket = socketFactory.createSocket(basicBehaviorServerHost, basicBehaviorServerPort, inetAddress , localPort);
               break;
            }
            catch (BindException e)
            {
               log.info("unable to bind to: " + localPort);
               localPort = random.nextInt() & 0xffff;
            }
            catch (Exception e)
            {
               log.error(e);
               e.printStackTrace();
               fail();
            }
         }
         
         assertTrue(socket.getLocalAddress().equals(inetAddress));
         assertTrue(socket.getLocalPort() == localPort);
         assertTrue(doOneSocketFactoryTest(socket));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      try
      {
         InetAddress inetAddress = InetAddress.getByName(basicBehaviorServerHost);
         socket = socketFactory.createSocket(inetAddress, basicBehaviorServerPort);
         assertTrue(doOneSocketFactoryTest(socket));
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      
      try
      {  
         while (true)
         {
            try
            {
               InetAddress remoteAddress = InetAddress.getByName(basicBehaviorServerHost);
               InetAddress localAddress = InetAddress.getByName("localhost");
               int localPort = random.nextInt() & 0xffff;
               socket = socketFactory.createSocket(remoteAddress, basicBehaviorServerPort, localAddress , localPort);
               assertTrue(socket.getLocalAddress().equals(localAddress));
               assertTrue(socket.getLocalPort() == localPort);
               assertTrue(doOneSocketFactoryTest(socket));
               break;
            }
            catch (BindException e)
            {
               log.info("looking for a free socket");
            }
         }
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      log.info("testSocketFactory() PASSES");
   }
   
   
/**
 *  FIXME Comment this
 * 
 * @param socket
 * @return
 */
   protected boolean doOneSocketFactoryTest(Socket socket)
   {
      log.info("entering doOneFactoryTest()");
      boolean success = true;
      
      try
      {         
         byte[] script = new byte[]
                                  { 
               READ,
               WRITE,
               READ,
               CLOSE_TEST_SOCKET
                                  };
         
         scriptStream.write(script.length);
         scriptStream.write(script); 
      }
      catch (IOException e)
      {
         log.error(e);
      }
      
      InputStream is = null;
      OutputStream os = null;
      
      try
      {
         is = socket.getInputStream();
         os = socket.getOutputStream();
         os.write(3);
         assertTrue(is.read() == 3);
         os.write(5);
         socket.close();
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      return success;
   }
   
   
   public void testMultipleManagers()
   {
      log.info("entering testMultipleManagers()");
      
      try
      {
         byte[] script = new byte[] {RUN_VIRTUALSERVERSOCKET, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script); 
         
         is = testSocket.getInputStream();
         is.read();
         Socket virtualSocket1 = new VirtualSocket(basicBehaviorServerHost, basicBehaviorServerPort + 1);
         Socket virtualSocket2 = new VirtualSocket(basicBehaviorServerHost, basicBehaviorServerPort + 1);
         is.read();
         Socket virtualSocket3 = new VirtualSocket(basicBehaviorServerHost, basicBehaviorServerPort + 1);
         os = virtualSocket3.getOutputStream();
         DataInputStream dis = new DataInputStream(virtualSocket3.getInputStream());
         int correctPort = dis.readInt();
         log.info("correct port: " + correctPort);
         assertTrue((virtualSocket3.getPort() == correctPort));

         virtualSocket1.close();
         virtualSocket2.close();
         virtualSocket3.close();
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      log.info("testMultipleManagers() PASSES");
   }
   
   
   public void testTimeout()
   {
      log.info("entering testTimeouts()");
      
      try
      {
         byte[] script = new byte[]
         {
            WRITE,
            WRITE,
            READ,
            SLEEP, 0x02, 0x00, // sleep 512 milliseconds
            WRITE,
            READ,
            SLEEP, 0x08, 0x00, // sleep 2048 milliseconds
            READ,
            WRITE,
            READ,
            WRITE,
            WRITE,
            READ,
            CLOSE_TEST_SOCKET
         };
         
         scriptStream.write(script.length);
         scriptStream.write(script); 
         
         is = testSocket.getInputStream();
         os = testSocket.getOutputStream();
         is.read();
         testSocket.setSoTimeout(2048);
         is.read(); // byte should already be available
         os.write(3);
         is.read(); // should have to wait, then get byte
         os.write(5);
         
         try
         {
            is.read();  // should timeout
            fail();
         }
         catch (SocketTimeoutException e)
         {
            if (e.getMessage().equals("Read timed out"))
               log.info("received expected timeout");
            else
            {
               log.error("received unexpected exception", e);
               fail();
            }
         }
         
         os.write(7);
         testSocket.setSoTimeout(0); 
         
         // The 7 should have been read before setSoTimeout(0), but should still be available.
         assertTrue(is.read() == 7);
         
         testSocket.setSoTimeout(1024);
         byte[] bytes = new byte[3];
         os.write(9);
         int n = is.read(bytes, 0, 3);
         log.info("n = " + n);
         assertTrue((n == 1 || n == 2));
         
         if (n == 1)
         {
            testSocket.setSoTimeout(0);
            is.read();
            testSocket.setSoTimeout(1024);
         }
         
         os.write(11);
         Thread.sleep(1000);
         assertTrue(is.read() == -1);
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      log.info("testTimeouts() PASSES");
   }
   

   static class C implements Serializable
   {
      static final int SIZE = 64 * 1024;
      byte[] bytes;
      C()
      {
         bytes = new byte[SIZE];
         for (int i = 0; i < SIZE; i++)
            bytes[i] = (byte) i;
      }
      C(InputStream is) throws IOException
      {
         bytes = new byte[SIZE];
         int total = 0;
         int n = 0;
         while (total < SIZE && n >= 0)
         {
            n = is.read(bytes, total, SIZE-total);
            total += n;
         }
         for (int i = 0; i < SIZE; i++)
            if ((0xff & bytes[i]) != (i % 256))
               log.info("read discrepancy [" + i + "]: " + (0xff & bytes[i]) + " <->" + i % 256);
      }
      void write(OutputStream os) throws IOException
      {
         os.write(bytes);  
      }
      boolean equals(C c)
      {
         boolean result = true;
         
         for (int i = 0; i < SIZE; i++)
         {
            if (bytes[i] != c.bytes[i])
            {
               log.info("equals discrepancy [" + i + " (" + (i % 256) + "]: " + (0xff & bytes[i]) + " != " + (0xff & c.bytes[i]));
               result = false;
            }
         }
         return result;
      } 
      static void echo(InputStream is, OutputStream os) throws IOException
      {
         byte[] tempBytes = new byte[SIZE];
         int total = 0;
         int n = 0;
         while (total < SIZE && n >= 0)
         {
            n = is.read(tempBytes, total, SIZE-total);
            total += n;
         }
         for (int i = 0; i < SIZE; i++)
            if ((0xff & tempBytes[i]) != (i % 256))
               log.info("echo discrepancy [" + i + "]: " + (0xff & tempBytes[i]) + " <->" + i % 256);
            
         os.write(tempBytes);
      }
   }

   
   public void testLongMessages()
   {
      log.info("entering testLongMessages()");
  
      try
      {
         byte[] script = new byte[] {RUN_LONG_MESSAGE_TEST, WRITE, CLOSE_TEST_SOCKET};
         scriptStream.write(script.length);
         scriptStream.write(script);
      }
      catch (IOException e1)
      {
         log.error(e1);
      }
      
      try
      {
         is = testSocket.getInputStream();
         is.read();
         int port = ((VirtualSocket) testSocket).getPort();
         VirtualSocket vs1 = new VirtualSocket(clientServerSocketHost, port);
         VirtualSocket vs2 = new VirtualSocket(clientServerSocketHost, port);
         VirtualSocket vs3 = new VirtualSocket(clientServerSocketHost, port);
         final C c = new C();
         final OutputStream os1 = vs1.getOutputStream();
         final OutputStream os2 = vs2.getOutputStream();
         final OutputStream os3 = vs3.getOutputStream();
         
         Thread t1 = new Thread()
         {
            public void run()
            {
              try
              {
                 c.write(os1);
                 c.write(os2);
                 c.write(os3);
              }
              catch (Exception e)
              {
                 log.error(e);
              }
            }
         };
         
         
         Thread t2 = new Thread()
         {
            public void run()
            {
              try
              {
                 c.write(os1);
                 c.write(os2);
                 c.write(os3);
              }
              catch (Exception e)
              {
                 log.error(e);
              }
            }
         };
 
         
         Thread t3 = new Thread()
         {
            public void run()
            {
              try
              {
                 c.write(os1);
                 c.write(os2);
                 c.write(os3);
              }
              catch (Exception e)
              {
                 log.error(e);
              }
            }
         };
         
         t1.start();
         t2.start();
         t3.start();
         t1.join();
         t2.join();
         t3.join();
         InputStream is1 = vs1.getInputStream();
         InputStream is2 = vs2.getInputStream();
         InputStream is3 = vs3.getInputStream();
         assertTrue(new C(is1).equals(c));
         assertTrue(new C(is1).equals(c));
         assertTrue(new C(is1).equals(c));
         assertTrue(new C(is2).equals(c));
         assertTrue(new C(is2).equals(c));
         assertTrue(new C(is2).equals(c));
         assertTrue(new C(is3).equals(c));
         assertTrue(new C(is3).equals(c));
         assertTrue(new C(is3).equals(c));
         vs1.close();
         vs2.close();
         vs3.close();
         
         // Give VSS on server time to unregister on this side.
         MultiplexingManager manager = vs1.getMultiplexingManager();
         
         while (manager.isRemoteServerSocketRegistered())
         {
            try
            {
               Thread.sleep(500);
            }
            catch (InterruptedException ignored) {}
         }
      }
      catch (Exception e)
      {
         log.error(e);
         e.printStackTrace();
         fail();
      }
      
      log.info("testLongMessages() PASSES");
   }
   
   
   static class ReaderThread1 extends Thread
   {
      InputStream is;
      int id;
      int counter;
      int n;
      boolean running;
      ReaderThread1(InputStream is, int id) {this.is = is; this.id = id;}
      public void run()
      {
         try
         {
            running = true;
            while ((n = is.read()) != -1)
            {
               counter++;
               log.debug(id + ": " + counter + ": " + (0xff & n));
               sleep(10);
            }
            log.debug("done: " + id);
            running = false;
         }
         catch (Exception e)
         {
            log.error(e);
            e.printStackTrace();
         }
      }
      int getCounter() {return counter;}
      boolean isRunning() {return running;}
   }
   
   static class ReaderThread2 extends Thread
   {
      InputStream is;
      int id;
      int counter;
      int n;
      byte[] bytes = new byte[2];
      boolean running;
      ReaderThread2(InputStream is, int id) {this.is = is; this.id = id;}
      public void run()
      {
         running = true;
         try
         {
            while ((n = is.read(bytes)) != -1)
            {
               counter += n;
               log.debug(id + ": " + n + ", " + counter + ": " + (0xff & bytes[0]));
               sleep(10);
            }
            log.debug("done: " + id);
            running = false;
         }
         catch (Exception e)
         {
            log.error(e);
            e.printStackTrace();
         }
      }
      int getCounter() {return counter;}
      boolean isRunning() {return running;}
   }
   
   static class SkipThread extends Thread
   {
      InputStream is;
      int id;
      int n;
      int counter;
      boolean running;
      SkipThread(InputStream is, int id) {this.is = is; this.id = id;}
      public void run()
      {
         running = true;
         try
         {
            while ((n = (int) is.skip(3)) != 0)
            {
               counter += n;
               log.debug(id + ": " + n + ", " + counter);
               sleep(10);
            }
            log.debug("done: " + id);
            running = false;
         }
         catch (Exception e)
         {
            log.error(e);
            e.printStackTrace();
         }
      }
      int getCounter() {return counter;}
      boolean isRunning() {return running;}
   }
   
   public void testMultipleReadersCloseEarly()
   {
      log.info("entering testMultipleReadersCloseEarly()");
      
          try
          {
             byte[] script = new byte[] {RUN_MULTIPLE_READERS_TEST, CLOSE_TEST_SOCKET};
             scriptStream.write(script.length);
             scriptStream.write(script);
          }
          catch (IOException e1)
          {
             log.error(e1);
          }
          
          try
          {
             is = testSocket.getInputStream();
             ReaderThread1 rt1 = new ReaderThread1(is, 1);
             ReaderThread2 rt2 = new ReaderThread2(is, 2);
             SkipThread st = new SkipThread(is, 3);
             rt1.start();
             rt2.start();
             st.start();
             rt1.join();
             rt2.join();
             st.join();
             assertTrue(rt1.getCounter() + rt2.getCounter() + st.getCounter() == 1000);
             assertFalse(rt1.isRunning());
             assertFalse(rt2.isRunning());
             assertFalse(st.isRunning());
          }
          catch (Exception e)
          {
             log.error(e);
             e.printStackTrace();
             fail();
          }
          
          log.info("testMultipleReadersCloseEarly() PASSES");
   }
   
   
   public void testMultipleReadersCloseLate()
   {
      log.info("entering testMultipleReadersCloseLate()");
      
          try
          {
             byte[] script = new byte[]
             {
                RUN_MULTIPLE_READERS_TEST,
                READ,
                SHUTDOWN_OUTPUT,
                READ,
                CLOSE_TEST_SOCKET};
             scriptStream.write(script.length);
             scriptStream.write(script);
          }
          catch (IOException e1)
          {
             log.error(e1);
          }
          
          try
          {
             is = testSocket.getInputStream();
             log.info("timeout: " + testSocket.getSoTimeout());
             ReaderThread1 rt1 = new ReaderThread1(is, 1);
             ReaderThread2 rt2 = new ReaderThread2(is, 2);
             SkipThread st = new SkipThread(is, 3);
             rt1.start();
             rt2.start();
             st.start();

             while (rt1.getCounter() + rt2.getCounter() + st.getCounter() < 1000)
             {
                Thread.sleep(500);
             }
             
             assertTrue(rt1.getCounter() + rt2.getCounter() + st.getCounter() == 1000);
             assertTrue(rt1.isRunning());
             assertTrue(rt2.isRunning());
             assertTrue(st.isRunning());
             
             os = testSocket.getOutputStream();
             is = testSocket.getInputStream();
             // Tell server to shut down output.
             os.write(3);
             
             while(rt1.isRunning() || rt2.isRunning() || st.isRunning())
             {  
                Thread.sleep(500);
             }
             
             assertFalse(rt1.isRunning());
             assertFalse(rt2.isRunning());
             assertFalse(st.isRunning());
             os.write(5);
          }
          catch (Exception e)
          {
             log.error(e);
             e.printStackTrace();
             fail();
          }
          
          log.info("testMultipleReadersCloseLate() PASSES");
   }
}

