package com.limegroup.gnutella.downloader; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import junit.framework.Test; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.GUID; import com.limegroup.gnutella.MessageListener; import com.limegroup.gnutella.PushEndpoint; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.UDPPinger; import com.limegroup.gnutella.UDPReplyHandler; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.vendor.HeadPing; import com.limegroup.gnutella.messages.vendor.HeadPong; import com.limegroup.gnutella.settings.DownloadSettings; import com.limegroup.gnutella.stubs.MessageRouterStub; import com.limegroup.gnutella.util.BaseTestCase; import com.limegroup.gnutella.util.Cancellable; import com.limegroup.gnutella.util.IntervalSet; import com.limegroup.gnutella.util.IpPort; import com.limegroup.gnutella.util.IpPortImpl; import com.limegroup.gnutella.util.PrivilegedAccessor; /** * tests the functioning of the ping ranker, i.e. how it sends out headpings * and how it ranks hosts based on the returned results. * */ public class PingRankerTest extends BaseTestCase { public PingRankerTest(String name) { super(name); } public static Test suite() { return buildTestSuite(PingRankerTest.class); } static MockPinger pinger; static PingRanker ranker; /** * file descs for the partial and complete files * * */ public static void globalSetUp() { // set up a mock pinger pinger = new MockPinger(); } public void setUp() throws Exception { pinger.messages.clear(); pinger.hosts.clear(); ranker = new PingRanker(); PrivilegedAccessor.setValue(ranker,"pinger",pinger); PrivilegedAccessor.setValue(RouterService.class,"router", new MessageRouterStub()); PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.FALSE); ranker.setMeshHandler(new MockMesh(ranker)); DownloadSettings.WORKER_INTERVAL.setValue(1); DownloadSettings.MAX_VERIFIED_HOSTS.revertToDefault(); DownloadSettings.PING_BATCH.revertToDefault(); } /** * Tests that the ranker sends out a HeadPing requesting ranges and alts to given hosts. */ public void testPingsNewHosts() throws Exception { for (int i =1;i <= 10;i++) { ranker.addToPool(newRFDWithURN("1.2.3."+i,3)); Thread.sleep(20); } assertEquals(10,pinger.hosts.size()); assertEquals(10,pinger.messages.size()); for (int i = 0 ;i < 10; i++) { pinger.hosts.contains(newRFDWithURN("1.2.3."+i,3)); HeadPing ping = (HeadPing) pinger.messages.get(i); assertTrue(ping.requestsRanges()); assertTrue(ping.requestsAltlocs()); } } /** * Tests that the ranker stops looking for new hosts once it has found enough. */ public void testStopsPinging() throws Exception { DownloadSettings.MAX_VERIFIED_HOSTS.setValue(1); ranker.addToPool(newRFDWithURN("1.2.3.4",3)); assertEquals(1,pinger.hosts.size()); // get a reply from that host MockPong pong = new MockPong(true,true,-1,false,false,true,null,null,null); ranker.processMessage(pong,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); // add some more hosts for (int i =1;i <= 10;i++) ranker.addToPool(newRFDWithURN("1.2.3."+i,3)); // no more pings should have been sent out. assertEquals(1,pinger.hosts.size()); // consume the host we know about ranker.getBest(); // we should send out some more pings assertGreaterThan(1,pinger.hosts.size()); } /** * Tests that the ranker learns about new hosts from altlocs */ public void testLearnsFromAltLocs() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); RemoteFileDesc original = newRFDWithURN("1.2.3.4",3); ranker.addToPool(original); assertEquals(1,pinger.hosts.size()); pinger.hosts.clear(); // send two altlocs, one containing the node itself IpPort ip = new IpPortImpl("1.2.3.5",1); Set alts = new HashSet(); alts.add(ip); //and one push loc PushEndpoint pe =new PushEndpoint((new GUID(GUID.makeGuid())).toHexString()+";1.2.3.6:7"); Set push = new HashSet(); push.add(pe); MockPong pong = new MockPong(true,true,1,false,false,false,null,alts,push); MockMesh mesh = new MockMesh(ranker); ranker.setMeshHandler(mesh); ranker.processMessage(pong,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); assertNotNull(mesh.sources); ranker.addToPool(mesh.sources); // now the ranker should know about more than one host. // the best host should be the one that actually replied. RemoteFileDesc best = ranker.getBest(); assertEquals(original,best); // the ranker should have more available hosts, even if we haven't // pinged any. assertTrue(ranker.hasMore()); // the ranker should have pinged the other two hosts. assertEquals(2,pinger.hosts.size()); } /** * Tests that the ranker does not add altlocs it already knows about * either from other altlocs or from direct addition */ public void testIgnoresDuplicateAlts() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); RemoteFileDesc original = newRFDWithURN("1.2.3.4",3); GUID g = new GUID(GUID.makeGuid()); RemoteFileDesc original2 = newPushRFD(g.bytes(),"2.2.2.2:2;3.3.3.3:3","1.2.3.6:7"); ranker.addToPool(original); Thread.sleep(30); ranker.addToPool(original2); assertEquals(3,pinger.hosts.size()); pinger.hosts.clear(); // make one of the hosts send an altloc of itself (spammer?) and the pushloc IpPort ip = new IpPortImpl("1.2.3.4",1); PushEndpoint pe = new PushEndpoint(g.toHexString()+";7:1.2.3.6;4.4.4.4:4"); Set alts = new HashSet(); alts.add(ip); Set push = new HashSet(); push.add(pe); MockPong pong = new MockPong(true,true,-1,false,false,false,null,alts,push); ranker.processMessage(pong, new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); // both of the carried altlocs are dupes, so we should not have pinged anybody assertTrue(pinger.hosts.isEmpty()); } /** * Tests that the ranker sends a HeadPing to the push proxies of a firewalled * source as long as we are not firewalled or can do FWT */ public void testPingsFirewalledHosts() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); assertTrue(RouterService.acceptedIncomingConnection()); GUID g = new GUID(GUID.makeGuid()); ranker.addToPool(newPushRFD(g.bytes(),"1.2.2.2:3","2.2.2.3:5")); assertEquals(1,pinger.hosts.size()); assertIpPortEquals(new IpPortImpl("1.2.2.2",3),pinger.hosts.get(0)); HeadPing ping = (HeadPing)pinger.messages.get(0); assertEquals(g,ping.getClientGuid()); PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.FALSE); } /** * tests that we do not ping firewalled hosts if we cannot do FWT and are firewalled */ public void testSkipsFirewalledHosts() throws Exception { assertFalse(RouterService.acceptedIncomingConnection()); assertFalse(RouterService.getUdpService().canDoFWT()); GUID g = new GUID(GUID.makeGuid()); ranker.addToPool(newPushRFD(g.bytes(),"1.2.2.2:3","2.2.2.3:5")); assertEquals(0,pinger.hosts.size()); assertEquals(0,pinger.hosts.size()); } /** * We should drop unsolicited pongs. */ public void testIgnoresUnsolicitedPongs() throws Exception { // add a host ranker.addToPool(newRFDWithURN("1.2.3.4",3)); // receive a pong from another host MockPong pong = new MockPong(true,true,0,true,false,true,null,null,null); ranker.processMessage(pong, new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); // consume the first guy assertTrue(ranker.hasMore()); ranker.getBest(); // we shouldn't have any more hosts available assertFalse(ranker.hasMore()); } /** * When sending a ping to several push proxies, we may get replies * from more than one - only one should be processed. */ public void testMultipleProxyReplies() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); assertTrue(RouterService.acceptedIncomingConnection()); GUID g = new GUID(GUID.makeGuid()); ranker.addToPool(newPushRFD(g.bytes(),"1.2.2.2:3;1.3.3.3:4","2.2.2.3:5")); Thread.sleep(100); // two pings should be sent out assertEquals(2,pinger.hosts.size()); Set s = new TreeSet(IpPort.COMPARATOR); s.addAll(pinger.hosts); assertTrue(s.contains(new IpPortImpl("1.2.2.2",3))); assertTrue(s.contains(new IpPortImpl("1.3.3.3",4))); // receive one pong from each proxy MockPong pong = new MockPong(true,true,-1,true,false,true,null,null,null); ranker.processMessage(pong,new UDPReplyHandler(InetAddress.getByName("1.3.3.3"),4)); ranker.processMessage(pong,new UDPReplyHandler(InetAddress.getByName("1.2.2.2"),3)); // there should be only one host available to try assertTrue(ranker.hasMore()); ranker.getBest(); assertFalse(ranker.hasMore()); } /** * Tests that the ranker prefers hosts that have sent a pong back but in * case it runs out of verified hosts it returns a non-verified one. */ public void testPrefersPongedHost() throws Exception { assertFalse(ranker.hasMore()); List l = new ArrayList(10); for (int i =0;i < 10;i++) l.add(newRFDWithURN("1.2.3."+i,3)); ranker.addToPool(l); assertTrue(ranker.hasMore()); Thread.sleep(100); // send a pong back from a single host MockPong pong = new MockPong(true,true,0,true,false,true,null,null,null); ranker.processMessage(pong, new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); // now this host should be prefered over other hosts. RemoteFileDesc rfd = ranker.getBest(); assertEquals("1.2.3.5",rfd.getHost()); // but if we ask for more hosts we'll get some of the unverified ones assertTrue(ranker.hasMore()); rfd = ranker.getBest(); assertNotEquals("1.2.3.5",rfd.getHost()); assertTrue(rfd.getHost().startsWith("1.2.3.")); } /** * Tests that the ranker discards sources that claim they do not have the file. * and informs the mesh handler if such exists */ public void testDiscardsNoFile() throws Exception { RemoteFileDesc noFile = newRFDWithURN("1.2.3.4",3); ranker.addToPool(noFile); MockMesh handler = new MockMesh(ranker); ranker.setMeshHandler(handler); assertTrue(ranker.hasMore()); MockPong pong = new MockPong(false,true,0,true,false,true,null,null,null); assertFalse(pong.hasFile()); ranker.processMessage(pong,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); assertFalse(ranker.hasMore()); assertEquals(noFile,handler.rfd); assertFalse(handler.good); ranker.setMeshHandler(null); } /** * Tests that the ranker offers hosts that indicated they were busy last */ public void testBusyOfferedLast() throws Exception { List l = new ArrayList(); l.add(newRFDWithURN("1.2.3.4",3)); l.add(newRFDWithURN("1.2.3.5",3)); ranker.addToPool(l); MockPong busy = new MockPong(true,true,20,true,true,true,null,null,null); MockPong notBusy = new MockPong(true,true,0,true,false,true,null,null,null); ranker.processMessage(busy,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); ranker.processMessage(notBusy,new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); RemoteFileDesc best = ranker.getBest(); assertEquals("1.2.3.5",best.getHost()); // not busy best = ranker.getBest(); assertEquals("1.2.3.4",best.getHost()); // busy } /** * Tests that the ranker offers hosts that have more free slots first */ public void testSortedByQueueRank() throws Exception { List l = new ArrayList(); l.add(newRFDWithURN("1.2.3.4",3)); l.add(newRFDWithURN("1.2.3.5",3)); l.add(newRFDWithURN("1.2.3.6",3)); ranker.addToPool(l); MockPong oneFree = new MockPong(true,true,-1,true,false,true,null,null,null); MockPong noFree = new MockPong(true,true,0,true,false,true,null,null,null); MockPong oneQueue = new MockPong(true,true,1,true,false,true,null,null,null); ranker.processMessage(oneQueue,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); ranker.processMessage(oneFree,new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); ranker.processMessage(noFree,new UDPReplyHandler(InetAddress.getByName("1.2.3.6"),1)); RemoteFileDesc best = ranker.getBest(); assertEquals("1.2.3.5",best.getHost()); // one free slot best = ranker.getBest(); assertEquals("1.2.3.6",best.getHost()); // no free slots best = ranker.getBest(); assertEquals("1.2.3.4",best.getHost()); // one queued } /** * tests that within the same queue rank the firewalled hosts are preferred * if we can't do fwt */ public void testFirewalledPreferred() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); assertTrue(RouterService.acceptedIncomingConnection()); RemoteFileDesc open = newRFDWithURN("1.2.3.4",3); RemoteFileDesc openMoreSlots = newRFDWithURN("1.2.3.5",3); RemoteFileDesc push = newPushRFD(GUID.makeGuid(),"1.2.3.6:6",null); List l = new ArrayList(); l.add(open);l.add(openMoreSlots);l.add(push); ranker.addToPool(l); MockPong openPong = new MockPong(true,true,-1,false,false,true,null,null,null); MockPong pushPong = new MockPong(true,true,-1,true,false,true,null,null,null); MockPong openMorePong = new MockPong(true,true,-2,false,false,true,null,null,null); ranker.processMessage(openPong,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); ranker.processMessage(openMorePong,new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); ranker.processMessage(pushPong,new UDPReplyHandler(InetAddress.getByName("1.2.3.6"),6)); RemoteFileDesc best = ranker.getBest(); assertEquals("1.2.3.5",best.getHost()); // open with more slots best = ranker.getBest(); assertTrue(best.getPushProxies().contains(new IpPortImpl("1.2.3.6",6))); // firewalled best = ranker.getBest(); assertEquals("1.2.3.4",best.getHost()); // open } /** * tests that within the same rank and firewall status, partial sources are preferred */ public void testPartialPreferred() throws Exception { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",Boolean.TRUE); List l = new ArrayList(); l.add(newRFDWithURN("1.2.3.4",3)); l.add(newRFDWithURN("1.2.3.5",3)); l.add(newRFDWithURN("1.2.3.6",3)); l.add(newPushRFD(GUID.makeGuid(),"1.2.3.7:7",null)); ranker.addToPool(l); MockPong oneFree = new MockPong(true,true,-1,true,false,true,null,null,null); MockPong oneFreePartial = new MockPong(true,false,-1,true,false,true,new IntervalSet(),null,null); MockPong noSlotsFull = new MockPong(true,true,0,true,false,true,null,null,null); MockPong oneFreeOpen= new MockPong(true,true,-1,false,false,true,null,null,null); ranker.processMessage(noSlotsFull,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); ranker.processMessage(oneFreeOpen,new UDPReplyHandler(InetAddress.getByName("1.2.3.5"),1)); ranker.processMessage(oneFreePartial,new UDPReplyHandler(InetAddress.getByName("1.2.3.6"),1)); ranker.processMessage(oneFree,new UDPReplyHandler(InetAddress.getByName("1.2.3.7"),7)); RemoteFileDesc best = ranker.getBest(); assertTrue(best.getPushProxies().contains(new IpPortImpl("1.2.3.7",7))); // full, firewalled , one slot best = ranker.getBest(); assertEquals("1.2.3.6",best.getHost()); // partial, open, one slot best = ranker.getBest(); assertEquals("1.2.3.5",best.getHost()); // full, open, one slot best = ranker.getBest(); assertEquals("1.2.3.4",best.getHost()); // full, no slots, firewalled } /** * Tests that the ranker passes on to other rankers all the hosts it has been * told or learned about. */ public void testGetShareable() throws Exception { RemoteFileDesc rfd1, rfd2; rfd1 = newRFDWithURN("1.2.3.4",3); rfd2 = newRFDWithURN("1.2.3.5",3); ranker.addToPool(rfd1); ranker.addToPool(rfd2); Collection c = ranker.getShareableHosts(); assertTrue(c.contains(rfd1)); assertTrue(c.contains(rfd2)); assertEquals(2,c.size()); Thread.sleep(100); // tell the ranker about some altlocs through a headpong IpPort ip1, ip2; ip1 = new IpPortImpl("1.2.3.6",3); ip2 = new IpPortImpl("1.2.3.7",3); Set alts = new HashSet(); alts.add(ip1); alts.add(ip2); MockPong oneFreeOpen= new MockPong(true,true,-1,false,false,true,null,alts,null); ranker.processMessage(oneFreeOpen,new UDPReplyHandler(InetAddress.getByName("1.2.3.4"),1)); // the ranker should pass on the altlocs it discovered as well. c = ranker.getShareableHosts(); assertEquals(4,c.size()); TreeSet s = new TreeSet(IpPort.COMPARATOR); s.addAll(c); assertEquals(4,s.size()); assertTrue(s.contains(ip1)); assertTrue(s.contains(ip2)); assertTrue(s.contains(rfd1)); assertTrue(s.contains(rfd2)); } private static RemoteFileDesc newRFD(String host, int speed){ return new RemoteFileDesc(host, 1, 0, "asdf", TestFile.length(), new byte[16], speed, false, 4, false, null, null, false,false,"",0,null, -1); } private static RemoteFileDesc newRFDWithURN() { return newRFDWithURN(); } private static RemoteFileDesc newRFDWithURN(String host, int speed) { Set set = new HashSet(); try { // for convenience, don't require that they pass the urn. // assume a null one is the TestFile's hash. set.add(TestFile.hash()); } catch(Exception e) { fail("SHA1 not created"); } return new RemoteFileDesc(host, 1, 0, "asdf", TestFile.length(), new byte[16], speed, false, 4, false, null, set, false, false,"",0,null, -1); } /** * constructs an rfd for testing. if the host parameter is not null, the * rfd indicates FWT capability */ private static RemoteFileDesc newPushRFD(byte [] guid,String proxy, String host) throws IOException{ GUID g = new GUID(guid); String s = g.toHexString(); if (host != null) s = s+";fwt/1.0;" +host.substring(host.indexOf(":")+1)+":"+host.substring(0,host.indexOf(":")); else host = "1.1.1.1"; s =s+ ";"+proxy; PushEndpoint pe = new PushEndpoint(s); RemoteFileDesc ret = newRFDWithURN(host,3); ret = new RemoteFileDesc(ret,pe); return ret; } /** * a mock pinger. Note that the base code will still register messsage listeners * but we don't care because they will never be used. */ static class MockPinger extends UDPPinger { /** * the list of messages that was sent */ public List messages = new ArrayList(); /** * the list of hosts that we pinged, same order as messages */ public List hosts = new ArrayList(); public void rank(Collection hosts, MessageListener listener, Cancellable canceller, Message message) { for (Iterator iter = hosts.iterator(); iter.hasNext();) { this.hosts.add(iter.next()); messages.add(message); } } } /** * a very customizable HeadPong */ static class MockPong extends HeadPong { private Set altLocs, pushLocs; private boolean have, full, firewalled, busy, downloading; private IntervalSet ranges; private int queueStatus; public MockPong(boolean have, boolean full, int queueStatus, boolean firewalled, boolean busy, boolean downloading, IntervalSet ranges, Set altLocs, Set pushLocs) throws IOException{ super(new HeadPing(URN.createSHA1Urn("urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFE"))); this.altLocs = altLocs; this.pushLocs = pushLocs; this.queueStatus = queueStatus; this.have = have; this.full = full; this.firewalled = firewalled; this.busy = busy; this.downloading = downloading; this.ranges = ranges; } public Set getAllLocsRFD(RemoteFileDesc original) { Set ret = new HashSet(); if (altLocs!=null) for(Iterator iter = altLocs.iterator();iter.hasNext();) { IpPort current = (IpPort)iter.next(); ret.add(new RemoteFileDesc(original,current)); } if (pushLocs!=null){ for(Iterator iter = pushLocs.iterator();iter.hasNext();) { PushEndpoint current = (PushEndpoint)iter.next(); ret.add(new RemoteFileDesc(original,current)); } } return ret; } public Set getAltLocs() { return this.altLocs; } public Set getPushLocs() { return pushLocs; } public int getQueueStatus() { return queueStatus; } public IntervalSet getRanges() { return ranges; } public boolean hasCompleteFile() { return full; } public boolean hasFile() { return have; } public boolean isBusy() { return busy; } public boolean isDownloading() { return downloading; } public boolean isFirewalled() { return firewalled; } public boolean isGGEPPong() { return true; } } private static void assertIpPortEquals(IpPort a, Object b) { assertTrue(IpPort.COMPARATOR.compare(a,b) == 0); } static class MockMesh implements MeshHandler { private final SourceRanker ranker; public MockMesh(SourceRanker ranker) { this.ranker = ranker; } public boolean good; public RemoteFileDesc rfd; public Collection sources; public void informMesh(RemoteFileDesc rfd, boolean good) { this.rfd = rfd; this.good = good; } public void addPossibleSources(Collection c) { sources = c; ranker.addToPool(c); } } }