package com.limegroup.gnutella; import java.io.File; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import junit.framework.Test; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PushRequest; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.settings.FilterSettings; import com.limegroup.gnutella.settings.SharingSettings; import com.limegroup.gnutella.settings.UltrapeerSettings; import com.limegroup.gnutella.settings.UploadSettings; import com.limegroup.gnutella.stubs.ActivityCallbackStub; import com.limegroup.gnutella.util.BaseTestCase; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.PrivilegedAccessor; import com.limegroup.gnutella.xml.MetaFileManager; public class MulticastTest extends BaseTestCase { private static ActivityCallback CALLBACK; private static final int DELAY = 1000; private static MetaFileManager FMAN; private static MulticastMessageRouter MESSAGE_ROUTER; private static RouterService ROUTER_SERVICE; private static final String MP3_NAME = "com/limegroup/gnutella/metadata/mpg2layII_1504h_16k_frame56_24000hz_joint_CRCOrigID3v1&2_test27.mp3"; public MulticastTest(String name) { super(name); } public static Test suite() { return buildTestSuite(MulticastTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } private static void setSettings() throws Exception { FilterSettings.BLACK_LISTED_IP_ADDRESSES.setValue( new String[] {"*.*.*.*"}); // Set the local host to not be banned so pushes can go through String ip = InetAddress.getLocalHost().getHostAddress(); FilterSettings.WHITE_LISTED_IP_ADDRESSES.setValue( new String[] {ip}); ConnectionSettings.PORT.setValue(TEST_PORT); SharingSettings.EXTENSIONS_TO_SHARE.setValue("mp3;"); File mp3 = CommonUtils.getResourceFile(MP3_NAME); assertTrue(mp3.exists()); CommonUtils.copy(mp3, new File(_sharedDir, "metadata.mp3")); ConnectionSettings.CONNECT_ON_STARTUP.setValue(false); ConnectionSettings.DO_NOT_BOOTSTRAP.setValue(true); UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(true); UltrapeerSettings.DISABLE_ULTRAPEER_MODE.setValue(false); UltrapeerSettings.FORCE_ULTRAPEER_MODE.setValue(true); UltrapeerSettings.MAX_LEAVES.setValue(1); ConnectionSettings.NUM_CONNECTIONS.setValue(3); ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false); ConnectionSettings.USE_GWEBCACHE.setValue(false); ConnectionSettings.WATCHDOG_ACTIVE.setValue(false); ConnectionSettings.MULTICAST_PORT.setValue(9021); ConnectionSettings.ALLOW_MULTICAST_LOOPBACK.setValue(true); } public static void globalSetUp() throws Exception { CALLBACK = new ActivityCallbackStub(); FMAN = new MetaFileManager(); MESSAGE_ROUTER = new MulticastMessageRouter(); ROUTER_SERVICE = new RouterService( CALLBACK, MESSAGE_ROUTER, FMAN); setSettings(); ROUTER_SERVICE.start(); RouterService.clearHostCatcher(); RouterService.connect(); // MUST SLEEP TO LET THE FILE MANAGER INITIALIZE sleep(2000); } public void setUp() throws Exception { setSettings(); MESSAGE_ROUTER.multicasted.clear(); MESSAGE_ROUTER.unicasted.clear(); assertEquals("unexpected number of shared files", 1, FMAN.getNumFiles() ); } public static void globalTearDown() throws Exception { RouterService.disconnect(); } private static void sleep(int time) { try { Thread.sleep(time); } catch(InterruptedException e) {} } /** * Tests that a multicast message is sent by utilizing the 'loopback' * feature of multicast. Notably, we receive the message even if we * sent it. */ public void testQueryIsSentAndReceived() { byte[] guid = RouterService.newQueryGUID(); RouterService.query(guid, "sam"); // sleep to let the search go. sleep(DELAY); assertEquals( "unexpected number of multicast messages", 1, MESSAGE_ROUTER.multicasted.size() ); Message m = (Message)MESSAGE_ROUTER.multicasted.get(0); assertInstanceof( QueryRequest.class, m ); QueryRequest qr = (QueryRequest)m; assertEquals("unexpected query", "sam", qr.getQuery() ); assertTrue( "should be multicast", qr.isMulticast() ); assertTrue( "should not be firewalled", !qr.isFirewalledSource() ); // note it was hopped once. assertEquals("wrong ttl", 0, qr.getTTL()); assertEquals("wrong hops", 1, qr.getHops()); } /** * Tests that replies to multicast queries contain the MCAST GGEP header * and other appropriate info. */ public void testQueryReplies() throws Exception { byte[] guid = RouterService.newQueryGUID(); RouterService.query(guid, "metadata"); // search for the name // sleep to let the search process. sleep(DELAY); assertEquals( "unexpected number of multicast messages", 1, MESSAGE_ROUTER.multicasted.size() ); assertEquals( "unexpected number of replies", 1, MESSAGE_ROUTER.unicasted.size() ); Message m = (Message)MESSAGE_ROUTER.unicasted.get(0); assertInstanceof( QueryReply.class, m); QueryReply qr = (QueryReply)m; assertTrue("should have MCAST header", qr.isReplyToMulticastQuery()); assertTrue("should not need push", !qr.getNeedsPush()); assertTrue("should not be busy", !qr.getIsBusy()); assertEquals("should only have 1 result", 1, qr.getResultCount()); assertTrue("should be measured speed", qr.getIsMeasuredSpeed()); byte[] xml = qr.getXMLBytes(); assertNotNull("didn't have xml", xml); assertGreaterThan("xml too small", 10, xml.length); // remember it was hopped once assertEquals("wrong ttl", 0, qr.getTTL() ); assertEquals("wrong hops", 1, qr.getHops() ); // wipe out the address so the first addr == my addr check isn't used wipeAddress(qr); assertEquals("wrong qos", 4, qr.calculateQualityOfService(false)); assertEquals("wrong qos", 4, qr.calculateQualityOfService(true)); } /** * Tests that a push sent from a multicast RFD is sent via multicast. * This does NOT test that ManagedDownloader will actively push * multicast requests, nor does it check that we can parse * the incoming GIV. It also does not test to ensure that multicast * pushes are given priority over all other uploads. */ public void testPushSentThroughMulticast() throws Exception { // first go through some boring stuff to get a correct QueryReply // that we can convert to an RFD. byte[] guid = RouterService.newQueryGUID(); RouterService.query(guid, "metadata"); sleep(DELAY); assertEquals("should have sent query", 1, MESSAGE_ROUTER.multicasted.size()); assertEquals("should have gotten reply", 1, MESSAGE_ROUTER.unicasted.size()); Message m = (Message)MESSAGE_ROUTER.unicasted.get(0); assertInstanceof( QueryReply.class, m); QueryReply qr = (QueryReply)m; // Because we're acting as both the sender & receiver, our // routing tables are a little confused, so we must reset // the push route table to map the guid to ForMeReplyHandler // from a UDPReplyHandler reroutePush(qr.getClientGUID()); // okay, now we have a QueryReply to convert to an RFD. List responses = qr.getResultsAsList(); assertEquals("should only have 1 response", 1, responses.size()); Response res = (Response)responses.get(0); RemoteFileDesc rfd = res.toRemoteFileDesc(qr.getHostData()); assertTrue("rfd should be multicast", rfd.isReplyToMulticast()); // clear the data to make it easier to look at again... MESSAGE_ROUTER.multicasted.clear(); MESSAGE_ROUTER.unicasted.clear(); // Finally, we have the RFD we want to push. RouterService.getDownloadManager().sendPush(rfd); // sleep to make sure the push goes through. sleep(DELAY); assertEquals("should have sent & received push", 1, MESSAGE_ROUTER.multicasted.size()); // should be a push. m = (Message)MESSAGE_ROUTER.multicasted.get(0); assertInstanceof(PushRequest.class, m); PushRequest pr = (PushRequest)m; // note it was hopped. assertEquals("wrong ttl", 0, pr.getTTL()); assertEquals("wrong hops", 1, pr.getHops()); assertTrue("wrong client guid", Arrays.equals(rfd.getClientGUID(), pr.getClientGUID())); assertEquals("wrong index", rfd.getIndex(), pr.getIndex()); assertEquals("should not have unicasted anything", 0, MESSAGE_ROUTER.unicasted.size()); } /** * Tests to ensure multicast requests are sent via push * and will upload regardless of the slots left. */ public void testPushesHaveUploadPriority() throws Exception { // Make it so a normal upload request would fail. UploadSettings.UPLOADS_PER_PERSON.setValue(0); // first go through some boring stuff to get a correct QueryReply // that we can convert to an RFD. byte[] guid = RouterService.newQueryGUID(); RouterService.query(guid, "metadata"); sleep(DELAY); assertEquals("should have sent query", 1, MESSAGE_ROUTER.multicasted.size()); assertEquals("should have gotten reply", 1, MESSAGE_ROUTER.unicasted.size()); Message m = (Message)MESSAGE_ROUTER.unicasted.get(0); assertInstanceof( QueryReply.class, m); QueryReply qr = (QueryReply)m; // Because we're acting as both the sender & receiver, our // routing tables are a little confused, so we must reset // the push route table to map the guid to ForMeReplyHandler // from a UDPReplyHandler reroutePush(qr.getClientGUID()); // okay, now we have a QueryReply to convert to an RFD. List responses = qr.getResultsAsList(); assertEquals("should only have 1 response", 1, responses.size()); Response res = (Response)responses.get(0); RemoteFileDesc rfd = res.toRemoteFileDesc(qr.getHostData()); // clear the data to make it easier to look at again... MESSAGE_ROUTER.multicasted.clear(); MESSAGE_ROUTER.unicasted.clear(); assertFalse("file should not be saved yet", new File( _savedDir, "metadata.mp3").exists()); assertTrue("file should be shared", new File(_sharedDir, "metadata.mp3").exists()); RouterService.download(new RemoteFileDesc[] { rfd }, false, new GUID(guid)); // sleep to make sure the download starts & push goes through. sleep(10000); assertEquals("should have sent & received push", 1, MESSAGE_ROUTER.multicasted.size()); // should be a push. m = (Message)MESSAGE_ROUTER.multicasted.get(0); assertInstanceof(PushRequest.class, m); assertEquals("should not have unicasted anything", 0, MESSAGE_ROUTER.unicasted.size()); assertTrue("file should have been downloaded & saved", new File(_savedDir, "metadata.mp3").exists()); // Get rid of this file, so the -Dtimes=X option works properly... =) assertEquals("unexpected number of shared files", 2, FMAN.getNumFiles() ); File temp=new File( _savedDir, "metadata.mp3"); if( temp.exists() ) { FMAN.removeFileIfShared(temp); temp.delete(); } sleep(2*DELAY); assertFalse( "file should have been deleted",temp.exists() ); assertEquals("unexpected number of shared files", 1, FMAN.getNumFiles() ); } private static void setGUID(Message m, GUID g) { try { PrivilegedAccessor.invokeMethod( m, "setGUID", g ); } catch(Exception e) { ErrorService.error(e); } } private static void wipeAddress(QueryReply qr) throws Exception { PrivilegedAccessor.setValue(qr, "_address", new byte[4]); } private static void reroutePush(byte[] guid) throws Exception { RouteTable rt = (RouteTable)PrivilegedAccessor.getValue( MESSAGE_ROUTER, "_pushRouteTable"); rt.routeReply(guid, ForMeReplyHandler.instance()); } private static class MulticastMessageRouter extends StandardMessageRouter { MulticastMessageRouter() { super(); } List multicasted = new LinkedList(); List unicasted = new LinkedList(); public void handleMulticastMessage(Message msg, InetSocketAddress addr) { multicasted.add(msg); // change the guid so we can pretend we've never seen it before if( msg instanceof QueryRequest ) setGUID( msg, new GUID(RouterService.newQueryGUID()) ); super.handleMulticastMessage(msg, addr); } public void handleUDPMessage(Message msg, InetSocketAddress addr) { unicasted.add(msg); super.handleUDPMessage(msg, addr); } } }