package com.limegroup.gnutella; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.ObjectOutputStream; import java.net.DatagramSocket; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import junit.framework.Test; import com.limegroup.gnutella.handshaking.HandshakeResponder; import com.limegroup.gnutella.handshaking.HandshakeResponse; import com.limegroup.gnutella.handshaking.HeaderNames; import com.limegroup.gnutella.handshaking.UltrapeerHeaders; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingReply; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.settings.SearchSettings; import com.limegroup.gnutella.settings.SharingSettings; import com.limegroup.gnutella.settings.UltrapeerSettings; import com.limegroup.gnutella.stubs.ActivityCallbackStub; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.PrivilegedAccessor; import com.limegroup.gnutella.xml.MetaFileManager; /** * Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do * redirects properly, etc. The test includes a leaf attached to 3 * Ultrapeers. */ public class CreationTimeCacheTest extends com.limegroup.gnutella.util.BaseTestCase { private static final int PORT=6669; private static final int TIMEOUT=3000; private static final byte[] ultrapeerIP= new byte[] {(byte)18, (byte)239, (byte)0, (byte)144}; private static final byte[] oldIP= new byte[] {(byte)111, (byte)22, (byte)33, (byte)44}; private static Connection[] testUPs = new Connection[4]; private static RouterService rs; private static MyActivityCallback callback; private static MyFileManager fm; private static URN hash1; private static URN hash2; private static URN hash3; private static URN hash4; static { try { hash1 = URN.createSHA1Urn("urn:sha1:GLIQY64M7FSXBSQEZY37FIM5QQSASUSH"); hash2 = URN.createSHA1Urn("urn:sha1:GLIQY64M7FSXBSQEZY37FIM5QQSANITA"); hash3 = URN.createSHA1Urn("urn:sha1:GLIQY64M7FSXBSQEZY37FIM5QQABOALT"); hash4 = URN.createSHA1Urn("urn:sha1:GLIQY64M7FSXBSQEZY37FIM5BERKELEY"); } catch (Exception terrible) {} } /** * Ultrapeer 1 UDP connection. */ private static DatagramSocket[] UDP_ACCESS; /** * File where urns (currently SHA1 urns) get persisted to */ private static final String CREATION_CACHE_FILE = "createtimes.cache"; private static final String FILE_PATH = "com/limegroup/gnutella/util"; private static FileDesc[] descs; /** * Constructs a new CreationTimeCacheTest with the specified name. */ public CreationTimeCacheTest(String name) { super(name); } public static Test suite() { return buildTestSuite(CreationTimeCacheTest.class); } /** * Runs this test individually. */ public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } private static void doSettings() { ConnectionSettings.PORT.setValue(PORT); ConnectionSettings.CONNECT_ON_STARTUP.setValue(false); UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(false); UltrapeerSettings.DISABLE_ULTRAPEER_MODE.setValue(true); UltrapeerSettings.FORCE_ULTRAPEER_MODE.setValue(false); ConnectionSettings.NUM_CONNECTIONS.setValue(0); ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false); SharingSettings.EXTENSIONS_TO_SHARE.setValue("txt;mp3"); // get the resource file for com/limegroup/gnutella File berkeley = CommonUtils.getResourceFile("com/limegroup/gnutella/berkeley.txt"); File susheel = CommonUtils.getResourceFile("com/limegroup/gnutella/susheel.txt"); File mp3 = CommonUtils.getResourceFile("com/limegroup/gnutella/metadata/mpg1layIII_0h_58k-VBRq30_frame1211_44100hz_joint_XingTAG_sample.mp3"); // now move them to the share dir CommonUtils.copy(berkeley, new File(_sharedDir, "berkeley.txt")); CommonUtils.copy(susheel, new File(_sharedDir, "susheel.txt")); CommonUtils.copy(mp3, new File(_sharedDir, "metadata.mp3")); // make sure results get through SearchSettings.MINIMUM_SEARCH_QUALITY.setValue(-2); } public static void globalSetUp() throws Exception { doSettings(); callback=new MyActivityCallback(); fm = new MyFileManager(); rs=new RouterService(callback, new StandardMessageRouter(), fm); assertEquals("unexpected port", PORT, ConnectionSettings.PORT.getValue()); rs.start(); rs.clearHostCatcher(); rs.connect(); Thread.sleep(1000); assertEquals("unexpected port", PORT, ConnectionSettings.PORT.getValue()); } public void setUp() throws Exception { doSettings(); } private static Connection connect(RouterService rs, int port, boolean ultrapeer) throws Exception { ServerSocket ss=new ServerSocket(port); rs.connectToHostAsynchronously("127.0.0.1", port); Socket socket = ss.accept(); ss.close(); socket.setSoTimeout(3000); InputStream in=socket.getInputStream(); String word=readWord(in); if (! word.equals("GNUTELLA")) throw new IOException("Bad word: "+word); HandshakeResponder responder; if (ultrapeer) { responder = new UltrapeerResponder(); } else { responder = new OldResponder(); } Connection con = new Connection(socket); con.initialize(null, responder); replyToPing(con, ultrapeer); return con; } /** * Acceptor.readWord * * @modifies sock * @effects Returns the first word (i.e., no whitespace) of less * than 8 characters read from sock, or throws IOException if none * found. */ private static String readWord(InputStream sock) throws IOException { final int N=9; //number of characters to look at char[] buf=new char[N]; for (int i=0 ; i<N ; i++) { int got=sock.read(); if (got==-1) //EOF throw new IOException(); if ((char)got==' ') { //got word. Exclude space. return new String(buf,0,i); } buf[i]=(char)got; } throw new IOException(); } private static void replyToPing(Connection c, boolean ultrapeer) throws Exception { // respond to a ping iff one is given. Message m = null; byte[] guid; try { while (!(m instanceof PingRequest)) { m = c.receive(500); } guid = ((PingRequest)m).getGUID(); } catch(InterruptedIOException iioe) { //nothing's coming, send a fake pong anyway. guid = new GUID().bytes(); } Socket socket = (Socket)PrivilegedAccessor.getValue(c, "_socket"); PingReply reply = PingReply.createExternal(guid, (byte)7, socket.getLocalPort(), ultrapeer ? ultrapeerIP : oldIP, ultrapeer); reply.hop(); c.send(reply); c.flush(); } ///////////////////////// Actual Tests //////////////////////////// private CreationTimeCache getCTC() throws Exception { final Class clazz = Class.forName("com.limegroup.gnutella.CreationTimeCache"); return (CreationTimeCache) PrivilegedAccessor.invokeConstructor(clazz, null); } /** Tests that the URN_MAP is derived correctly from the URN_TO_TIME_MAP */ public void testMapCreation() throws Exception { // mock up our own createtimes.txt Map toSerialize = new HashMap(); Long old = new Long(1); Long middle = new Long(2); Long young = new Long(3); toSerialize.put(hash1, old); toSerialize.put(hash2, middle); toSerialize.put(hash3, middle); toSerialize.put(hash4, young); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(_settingsDir, CREATION_CACHE_FILE))); oos.writeObject(toSerialize); oos.close(); // now have the CreationTimeCache read it in CreationTimeCache ctCache = getCTC(); Map TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(toSerialize, TIME_MAP); } /** Tests the getFiles().iterator() method. */ public void testGetFiles() throws Exception { // mock up our own createtimes.txt Map toSerialize = new HashMap(); Long old = new Long(1); Long middle = new Long(2); Long young = new Long(3); toSerialize.put(hash1, middle); toSerialize.put(hash2, young); toSerialize.put(hash3, old); toSerialize.put(hash4, middle); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(_settingsDir, CREATION_CACHE_FILE))); oos.writeObject(toSerialize); oos.close(); // now have the CreationTimeCache read it in CreationTimeCache ctCache = getCTC(); // is everything mapped correctly from URN to Long? assertEquals(ctCache.getCreationTime(hash1), middle); assertEquals(ctCache.getCreationTime(hash2), young); assertEquals(ctCache.getCreationTime(hash3), old); assertEquals(ctCache.getCreationTime(hash4), middle); { Iterator iter = ctCache.getFiles().iterator(); assertEquals(hash2, iter.next()); URN urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); assertEquals(hash3, iter.next()); assertFalse(iter.hasNext()); } { Iterator iter = ctCache.getFiles(4).iterator(); assertEquals(hash2, iter.next()); URN urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); assertEquals(hash3, iter.next()); assertFalse(iter.hasNext()); } { Iterator iter = ctCache.getFiles(3).iterator(); assertEquals(hash2, iter.next()); URN urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); assertFalse(iter.hasNext()); } { Iterator iter = ctCache.getFiles(2).iterator(); assertEquals(hash2, iter.next()); URN urn = (URN) iter.next(); assertTrue(urn.equals(hash1) || urn.equals(hash4)); assertFalse(iter.hasNext()); } { Iterator iter = ctCache.getFiles(1).iterator(); assertEquals(hash2, iter.next()); assertFalse(iter.hasNext()); } { try { Iterator iter = ctCache.getFiles(0).iterator(); } catch (IllegalArgumentException expected) {} } } /** Tests the getFiles().iterator() method. */ public void testSettersAndGetters() throws Exception { CreationTimeCache ctCache = null; Iterator iter = null; Map TIME_MAP = null; Long old = new Long(1); Long middle = new Long(2); Long young = new Long(3); deleteCacheFile(); // should be a empty cache // --------------------------- ctCache = getCTC(); assertFalse(ctCache.getFiles().iterator().hasNext()); TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(0, TIME_MAP.size()); ctCache.addTime(hash1, middle.longValue()); ctCache.commitTime(hash1); ctCache.persistCache(); iter = ctCache.getFiles().iterator(); assertEquals(hash1, iter.next()); assertFalse(iter.hasNext()); ctCache = null; // --------------------------- // should have one value // --------------------------- ctCache = getCTC(); iter = ctCache.getFiles().iterator(); assertEquals(hash1, iter.next()); assertFalse(iter.hasNext()); TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(1, TIME_MAP.size()); ctCache.addTime(hash2, old.longValue()); ctCache.commitTime(hash2); ctCache.addTime(hash3, young.longValue()); ctCache.commitTime(hash3); ctCache.addTime(hash4, middle.longValue()); ctCache.commitTime(hash4); ctCache.removeTime(hash1); ctCache.persistCache(); iter = ctCache.getFiles().iterator(); assertEquals(hash3, iter.next()); // just clear middle iter.next(); assertEquals(hash2, iter.next()); assertFalse(iter.hasNext()); ctCache = null; // --------------------------- // should have three values // --------------------------- ctCache = getCTC(); iter = ctCache.getFiles().iterator(); assertEquals(hash3, iter.next()); assertEquals(hash4, iter.next()); assertEquals(hash2, iter.next()); assertFalse(iter.hasNext()); TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(3, TIME_MAP.size()); ctCache.removeTime(hash3); ctCache.persistCache(); // --------------------------- // should have two values but exclude one // --------------------------- fm.setExcludeURN(hash4); ctCache = getCTC(); iter = ctCache.getFiles().iterator(); assertEquals(hash2, iter.next()); assertFalse(iter.hasNext()); TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(1, TIME_MAP.size()); ctCache = null; fm.clearExcludeURN(); // --------------------------- } public void testCommit() throws Exception { CreationTimeCache ctCache = null; Iterator iter = null; Map TIME_MAP = null; Long old = new Long(1); Long middle = new Long(2); Long young = new Long(3); deleteCacheFile(); // should be a empty cache // --------------------------- ctCache = getCTC(); assertFalse(ctCache.getFiles().iterator().hasNext()); TIME_MAP = (Map)PrivilegedAccessor.getValue(ctCache, "URN_TO_TIME_MAP"); assertEquals(0, TIME_MAP.size()); // --------------------------- // make sure that only after something is committed is it returned // via getFiles().iterator() // --------------------------- ctCache.addTime(hash1, middle.longValue()); iter = ctCache.getFiles().iterator(); assertFalse(iter.hasNext()); ctCache.commitTime(hash1); iter = ctCache.getFiles().iterator(); assertEquals(hash1, iter.next()); assertFalse(iter.hasNext()); // --------------------------- // make sure that commiting changes ordering.... // --------------------------- ctCache.addTime(hash2, young.longValue()); ctCache.addTime(hash3, old.longValue()); ctCache.commitTime(hash3); iter = ctCache.getFiles().iterator(); assertEquals(hash1, iter.next()); assertEquals(hash3, iter.next()); assertFalse(iter.hasNext()); ctCache.commitTime(hash2); iter = ctCache.getFiles().iterator(); assertEquals(hash2, iter.next()); assertEquals(hash1, iter.next()); assertEquals(hash3, iter.next()); assertFalse(iter.hasNext()); // --------------------------- } /** * Test read & write of map */ public void testPersistence() throws Exception { deleteCacheFile(); assertTrue("cache should not be present", !cacheExists() ); CreationTimeCache cache = CreationTimeCache.instance(); FileDesc[] descs = createFileDescs(); assertNotNull("should have some file descs", descs); assertGreaterThan("should have some file descs", 0, descs.length); cache.persistCache(); assertTrue("cache should now exist", cacheExists()); for( int i = 0; i < descs.length; i++) { Long cTime = cache.getCreationTime(descs[i].getSHA1Urn()); assertNotNull("file should be present in cache", cTime); } } private static FileDesc[] createFileDescs() throws Exception { File path = CommonUtils.getResourceFile(FILE_PATH); File[] files = path.listFiles(new FileFilter() { public boolean accept(File file) { return !file.isDirectory(); } }); FileDesc[] fileDescs = new FileDesc[files.length]; for(int i=0; i<files.length; i++) { Set urns = calculateAndCacheURN(files[i]); fileDescs[i] = new FileDesc(files[i], urns, i); CreationTimeCache.instance().addTime(fileDescs[i].getSHA1Urn(), files[i].lastModified()); } return fileDescs; } private static void deleteCacheFile() { File cacheFile = new File(_settingsDir, CREATION_CACHE_FILE); cacheFile.delete(); } /** * Convenience method for making sure that the serialized file exists. */ private static boolean cacheExists() { File cacheFile = new File(_settingsDir, CREATION_CACHE_FILE); return cacheFile.exists(); } ////////////////////////////////////////////////////////////////// private void drainAll() throws Exception { drainAll(testUPs); } private static byte[] myIP() { return new byte[] { (byte)127, (byte)0, 0, 1 }; } private static final boolean DEBUG = false; static void debug(String message) { if(DEBUG) System.out.println(message); } private static class UltrapeerResponder implements HandshakeResponder { public HandshakeResponse respond(HandshakeResponse response, boolean outgoing) { Properties props = new UltrapeerHeaders("127.0.0.1"); props.put(HeaderNames.X_DEGREE, "42"); return HandshakeResponse.createResponse(props); } public void setLocalePreferencing(boolean b) {} } private static class OldResponder implements HandshakeResponder { public HandshakeResponse respond(HandshakeResponse response, boolean outgoing) { Properties props=new Properties(); return HandshakeResponse.createResponse(props); } public void setLocalePreferencing(boolean b) {} } public static class MyActivityCallback extends ActivityCallbackStub { } public static class MyFileManager extends MetaFileManager { private FileDesc fd = null; public URN toExclude = null; public void setExcludeURN(URN urn) { toExclude = urn; } public void clearExcludeURN() { toExclude = null; } public FileDesc getFileDescForUrn(URN urn){ if (fd == null) { File cacheFile = new File(_settingsDir, CREATION_CACHE_FILE); Set urnSet = new HashSet(); urnSet.add(hash1); fd = new FileDesc(cacheFile, urnSet, 0); } if ((toExclude != null) && toExclude.equals(urn)) return null; else if (urn.equals(hash1) || urn.equals(hash2) || urn.equals(hash3) || urn.equals(hash4)) return fd; else return super.getFileDescForUrn(urn); } } }