package com.limegroup.gnutella; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; import java.util.Random; import java.util.Set; import junit.framework.Test; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage; import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessage; import com.limegroup.gnutella.messages.vendor.TCPConnectBackVendorMessage; import com.limegroup.gnutella.messages.vendor.UDPConnectBackVendorMessage; import com.limegroup.gnutella.search.HostData; import com.limegroup.gnutella.stubs.ActivityCallbackStub; import com.limegroup.gnutella.util.PrivilegedAccessor; /** * Checks whether leaves request redirects properly. * The test includes a leaf attached to two ultrapeers. */ public class ClientSideValidateIncomingTest extends ClientSideTestCase { protected static final int PORT=6669; protected static int TIMEOUT=1000; // should override super private static final long MY_EXPIRE_TIME = 6 * 1000; private static final long MY_WAIT_TIME = 500; private static final long MY_VALIDATE_TIME = 3 * 1000; private static byte[] cbGuid = null; public ClientSideValidateIncomingTest(String name) { super(name); } public static Test suite() { return buildTestSuite(ClientSideValidateIncomingTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } ///////////////////////// Actual Tests //////////////////////////// // THIS TEST SHOULD BE RUN FIRST!! public void testConnectBackRequestsSent() throws Exception { // send a MessagesSupportedMessage testUP[0].send(MessagesSupportedVendorMessage.instance()); testUP[0].flush(); testUP[1].send(MessagesSupportedVendorMessage.instance()); testUP[1].flush(); // we expect to get a TCPConnectBack request Message m = null; boolean gotTCP = false, gotUDP = false; do { m = testUP[0].receive(TIMEOUT); if (m instanceof TCPConnectBackVendorMessage) gotTCP = true; if (m instanceof UDPConnectBackVendorMessage) { cbGuid = m.getGUID(); gotUDP = true; } } while (!gotTCP || !gotUDP); // client side seems to follow the setup process A-OK } // This test checks that if _acceptedIncoming is false, connect back // messages are NOT sent public void testTCPExpireRequestsNotSent() throws Exception { drainAll(); for (int i = 0; i < 2; i++) { assertFalse(rs.acceptedIncomingConnection()); try { testUP[0].receive(TIMEOUT); } catch (InterruptedIOException expected) {} Thread.sleep(MY_EXPIRE_TIME+MY_VALIDATE_TIME); assertFalse(rs.acceptedIncomingConnection()); Thread.sleep(100); // now we should get the connect backs cuz it has been a while Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof TCPConnectBackVendorMessage)); } } // make an incoming to the servent, wait a little, and then make sure // it asks for a connect back again public void testTCPExpireRequestsSent() throws Exception { drainAll(); for (int i = 0; i < 2; i++) { Socket s = new Socket("localhost", PORT); s.close(); Thread.sleep(100); assertTrue(rs.acceptedIncomingConnection()); // wait until the expire time is realized Thread.sleep(MY_EXPIRE_TIME + MY_VALIDATE_TIME + 1000); // query the Acceptor - it should send off more requests assertFalse(rs.acceptedIncomingConnection()); Thread.sleep(100); Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof TCPConnectBackVendorMessage)) ; } } // make an incoming to the servent, wait a little, and then make sure // it asks for a connect back again public void testTCPInterleavingRequestsSent() throws Exception { drainAll(); Random rand = new Random(); for (int i = 0; i < 6; i++) { if (rand.nextBoolean()) { Socket s = new Socket("localhost", PORT); s.close(); Thread.sleep(100); assertTrue(rs.acceptedIncomingConnection()); } // wait until the expire time is realized Thread.sleep(MY_EXPIRE_TIME + MY_VALIDATE_TIME); // throw some randomness in there - if we get an incoming we should // not send messages out if (rand.nextBoolean()) { Socket s = new Socket("localhost", PORT); s.close(); Thread.sleep(100); assertTrue(rs.acceptedIncomingConnection()); try { testUP[0].receive(TIMEOUT); } catch (InterruptedIOException expected) {} } else { // query the Acceptor - it should send off more requests assertFalse(rs.acceptedIncomingConnection()); Thread.sleep(100); Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof TCPConnectBackVendorMessage)) ; } } } /** * Tests that if the leaf is connected to only one ultrapeer it will * send a few redundant requests */ public void testTCPRedundantRequestsSent() throws Exception { drainAllParallel(testUP); // wait some time - both UPs should get a single connect back //sleep Thread.sleep(MY_VALIDATE_TIME+1000); readNumConnectBacks(1,testUP[0], TIMEOUT); readNumConnectBacks(1,testUP[1], TIMEOUT); // leave only one connection open assertGreaterThan(1,rs.getConnectionManager().getNumInitializedConnections()); testUP[1].close(); Thread.sleep(500); assertEquals(1,rs.getConnectionManager().getNumInitializedConnections()); drainAll(); // sleep Thread.sleep(MY_VALIDATE_TIME+1000); // we should receive more than one connect back redirects readNumConnectBacks(ConnectionManager.CONNECT_BACK_REDUNDANT_REQUESTS,testUP[0],TIMEOUT); } private void readNumConnectBacks(int num,Connection conn, int timeout) throws Exception { Message m; for (int i = 0; i < num; i++) { do { m = conn.receive(timeout); } while (!(m instanceof TCPConnectBackVendorMessage)); } try { do { m = conn.receive(timeout); } while (!(m instanceof TCPConnectBackVendorMessage)); fail ("got extra message on "+conn); } catch (IOException expected) {} } // This test checks that if _acceptedUnsolicitedIncoming is false, connect // back messages are NOT sent public void testUDPExpireRequestsNotSent() throws Exception { drainAll(); UDPService udpServ = UDPService.instance(); for (int i = 0; i < 2; i++) { assertFalse(udpServ.canReceiveUnsolicited()); try { testUP[0].receive(TIMEOUT); } catch (InterruptedIOException expected) {} Thread.sleep(MY_EXPIRE_TIME+MY_VALIDATE_TIME); assertFalse(udpServ.canReceiveUnsolicited()); Thread.sleep(100); // now we should get the connect backs cuz it has been a while Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof UDPConnectBackVendorMessage)); cbGuid = m.getGUID(); } } // make an incoming to the servent, wait a little, and then make sure // it asks for a connect back again public void testUDPExpireRequestsSent() throws Exception { drainAll(); UDPService udpServ = UDPService.instance(); for (int i = 0; i < 2; i++) { // wait until the expire time is realized // drain the UDP buffer Message m = null; try { while (true) { m = testUP[0].receive(200); } } catch (InterruptedIOException drained) {} // get the UDP message do { m = testUP[0].receive(); } while (!(m instanceof UDPConnectBackVendorMessage)) ; Thread.sleep(1000); assertFalse(udpServ.canReceiveUnsolicited()); // get the UDP message but this time answer it do { m = testUP[0].receive(); } while (!(m instanceof UDPConnectBackVendorMessage)) ; cbGuid = ((UDPConnectBackVendorMessage)m).getConnectBackGUID().bytes(); // now connect back and it should switch on unsolicited DatagramSocket s = new DatagramSocket(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PingRequest ping = new PingRequest(cbGuid, (byte)1, (byte)1); ping.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, InetAddress.getLocalHost(), PORT); s.send(pack); s.close(); Thread.sleep(1000); assertTrue(udpServ.canReceiveUnsolicited()); } } // make an incoming to the servent, wait a little, and then make sure // it asks for a connect back again public void testUDPInterleavingRequestsSent() throws Exception { drainAll(); UDPService udpServ = UDPService.instance(); Random rand = new Random(); for (int i = 0; i < 6; i++) { if (rand.nextBoolean()) { DatagramSocket s = new DatagramSocket(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ReplyNumberVendorMessage vm = new ReplyNumberVendorMessage(new GUID(cbGuid), 1); vm.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, InetAddress.getLocalHost(), PORT); s.send(pack); s.close(); Thread.sleep(100); } // wait until the expire time is realized Thread.sleep(MY_EXPIRE_TIME + MY_VALIDATE_TIME); // throw some randomness in there - if we get an incoming we should // not send messages out if (udpServ.canReceiveUnsolicited()) { DatagramSocket s = new DatagramSocket(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ReplyNumberVendorMessage vm = new ReplyNumberVendorMessage(new GUID(cbGuid), 1); vm.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, InetAddress.getLocalHost(), PORT); s.send(pack); s.close(); Thread.sleep(100); assertTrue(udpServ.canReceiveUnsolicited()); try { testUP[0].receive(TIMEOUT); } catch (InterruptedIOException expected) {} } else { Thread.sleep(MY_VALIDATE_TIME); // query the Acceptor - it should send off more requests Thread.sleep(100); Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof UDPConnectBackVendorMessage)) ; cbGuid = ((UDPConnectBackVendorMessage)m).getConnectBackGUID().bytes(); // now connect back and it should switch on unsolicited DatagramSocket s = new DatagramSocket(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PingRequest ping = new PingRequest(cbGuid, (byte)1, (byte)1); ping.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, InetAddress.getLocalHost(), PORT); s.send(pack); s.close(); } } } ////////////////////////////////////////////////////////////////// public static void doSettings() { try { PrivilegedAccessor.setValue(Acceptor.class, "INCOMING_EXPIRE_TIME", new Long(MY_EXPIRE_TIME)); PrivilegedAccessor.setValue(Acceptor.class, "WAIT_TIME_AFTER_REQUESTS", new Long(MY_WAIT_TIME)); PrivilegedAccessor.setValue(Acceptor.class, "TIME_BETWEEN_VALIDATES", new Long(MY_VALIDATE_TIME)); } catch (Exception bad) { assertTrue(false); } } public static Integer numUPs() { return new Integer(2); } public static ActivityCallback getActivityCallback() { return new MyActivityCallback(); } private static byte[] myIP() { return new byte[] { (byte)192, (byte)168, 0, 1 }; } public static class MyActivityCallback extends ActivityCallbackStub { private RemoteFileDesc rfd = null; public RemoteFileDesc getRFD() { return rfd; } public void handleQueryResult(RemoteFileDesc rfd, HostData data, Set locs) { this.rfd = rfd; } } }