PageRenderTime 64ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/core/com/limegroup/gnutella/RouteTable.java

https://github.com/zootella/learning
Java | 876 lines | 182 code | 123 blank | 571 comment | 46 complexity | 73382bd30274172448701e768f1ccf00 MD5 | raw file
  1. // Commented for the Learning branch
  2. package com.limegroup.gnutella;
  3. import java.util.HashMap;
  4. import java.util.Iterator;
  5. import java.util.Map;
  6. import java.util.TreeMap;
  7. import com.limegroup.gnutella.search.ResultCounter;
  8. /**
  9. * A RouteTable maps Gnutella packet GUIDs to the remote computer that sent us the request packet, and will want the response packet when it arrives.
  10. *
  11. * === How routing in Gnutella works. ===
  12. *
  13. * A Gnutella program has several connections to remote computers also running Gnutella software.
  14. * Gnutella packets have message GUIDs that mark them unique.
  15. *
  16. * The types of Gnutella packets exist in pairs, with a request packet and its corresponding reply packet.
  17. * For instance, a ping is a request packet, and a pong is it's corresponding reply packet.
  18. * The other pair is for searching: a query is a request packet, and a query hit is it's corresponding reply packet.
  19. *
  20. * When we get a request packet like a ping or a query, we do 2 things.
  21. * First, we make a note of its message GUID and which connection it came from.
  22. * Then, we broadcast it forward, sending it down the other connections.
  23. *
  24. * Later, a response packet like a pong or a query hit will come back.
  25. * It will have the same message GUID as the ping or query it's a response to.
  26. * We look its GUID up in our list, and find out which connection wanted it.
  27. * With each computer doing this, reply packets get routed all the way back to the computer that sent the request.
  28. *
  29. * === How this RouteTable class lets LimeWire route Gnutella packets. ===
  30. *
  31. * A RouteTable maps message GUIDs to ReplyHandler objects.
  32. *
  33. * ReplyHandler is an interface that the ManagedConnection, UDPReplyHandler, and ForMeReplyHandler classes implement.
  34. * A ManagedConnection object represents a remote computer that we have a TCP socket Gnutella connection with.
  35. * A UDPReplyHandler object represents a remote computer that we've been exchanging UDP packets with.
  36. * The ForMeReplyHandler object represents us.
  37. * All 3 are ReplyHandler objects, and all 3 represent computers on the Internet running Gnutella software.
  38. *
  39. * The ReplyHandler interface requires methods like handlePingReply(pong).
  40. * Give that method a pong packet, and it will send it to the remote computer it represents.
  41. *
  42. * When LimeWire gets a request packet like a ping or a query, it adds its message GUID and the ReplyHandler it came from in a RouteTable.
  43. * Later, when LimeWire gets a response packet like a pong or a query hit, is seaches the RouteTable for the message GUID to see which computer it's for.
  44. *
  45. * === The RouteTable object that LimeWire uses. ===
  46. *
  47. * The MessageRouter object creates 4 RouteTable objects.
  48. * These are the only RouteTable objects that exist as LimeWire runs.
  49. *
  50. * private RouteTable _pingRouteTable = new RouteTable(2 * 60, MAX_ROUTE_TABLE_SIZE);
  51. * private RouteTable _queryRouteTable = new RouteTable(5 * 60, MAX_ROUTE_TABLE_SIZE);
  52. * private RouteTable _pushRouteTable = new RouteTable(7 * 60, MAX_ROUTE_TABLE_SIZE);
  53. * private RouteTable _headPongRouteTable = new RouteTable(10, MAX_ROUTE_TABLE_SIZE);
  54. *
  55. * _pingRouteTable is for ping and pong packets.
  56. * _queryRouteTable is for query and query hit packets.
  57. * _pushRouteTable is used differently than the others.
  58. * _headPongRouteTable is for head pong packets, a LimeWire vendor message that is not the same thing as a pong.
  59. *
  60. * === How to use a RouteTable. ===
  61. *
  62. * When you get a ping, add a new routing entry with:
  63. *
  64. * routeTable.routeReply(guid, replyHandler);
  65. *
  66. * guid is the ping's message GUID.
  67. * replyHandler is the connection that sent the ping to us.
  68. *
  69. * Later, when you get a pong, find out where to send it with:
  70. *
  71. * replyHandler = routeTable.getReplyHandler(guid);
  72. *
  73. * guid is the pong's message GUID, which is the same as the ping's that we got before.
  74. * replyHandler is the connection that sent us the corresponding ping, and will want this pong.
  75. *
  76. * === Classes related to RouteTable. ===
  77. *
  78. * Here are some classes and interfaces related to RouteTable, and that RouteTable uses:
  79. * RouteTable class
  80. * RouteTableEntry nested class
  81. * ReplyRoutePair nested class
  82. * ResultCounter interface
  83. *
  84. * A RouteTable object is a list that maps GUIDs to ReplyHandler objects.
  85. * The values under the GUIDs aren't actually ReplyHandler objects, but rather RouteTableEntry objects that lead to ReplyHandler objects and bundle transfer statistics.
  86. * ReplyRoutePair objects wrap a ReplyHandler with these transfer statistics.
  87. * getReplyHandler(byte[], int, short) returns a ReplyRoutePair to return a ReplyHandler with this additional information.
  88. * The ResultCounter interface is only implementd by one class in LimeWire, RouteTableEntry.
  89. * routeReply() and tryToRouteReply() return RouteTableEntry objects cast to their ResultCounter interface.
  90. *
  91. * === How RouteTable works. ===
  92. *
  93. * When you make a RouteTable, you give it a time interval, like 5 seconds.
  94. * The RouteTable has 2 maps inside, named new and old.
  95. * Every 5 seconds, it moves everything from new to old, and throws away the contents of old.
  96. * The purge() method does this.
  97. *
  98. * When you add a GUID and ReplyHandler, it goes into the new map.
  99. * When you look up a GUID, the RouteTable searches both maps.
  100. *
  101. * This is a very efficient way to keep only recent listings in the RouteTable.
  102. * A lucky GUID will get added right after the purge, and be in the RouteTable for almost 10 seconds.
  103. * An unlucky GUID will be added right before the purge, and be in the RouteTable for just more than 5 seconds.
  104. * Every listing will exist for an amount of time between the given interval, and twice that interval.
  105. * Exactly how long a listing exists is random, depending on when during the interval the program adds it.
  106. *
  107. * ===
  108. *
  109. * The reply routing table. Given a GUID from a reply message header,
  110. * this tells you where to route the reply. It is mutable mapping
  111. * from globally unique 16-byte message IDs to connections. Old
  112. * mappings may be purged without warning, preferably using a FIFO
  113. * policy. This class makes a distinction between not having a mapping
  114. * for a GUID and mapping that GUID to null (in the case of a removed
  115. * ReplyHandler).
  116. *
  117. * This class can also optionally keep track of the number of reply bytes
  118. * routed per guid. This can be useful for implementing fair flow-control
  119. * strategies.
  120. *
  121. * The obvious implementation of this class is a mapping from GUID to
  122. * ReplyHandler's. The problem with this representation is it's hard to
  123. * implement removeReplyHandler efficiently. You either have to keep the
  124. * references to the ReplyHandler (which wastes memory) or iterate through
  125. * the entire table to clean all references (which wastes time AND removes
  126. * valuable information for preventing duplicate queries).
  127. *
  128. * Instead we use a layer of indirection. _newMap/_oldMap maps GUIDs to
  129. * integers, which act as IDs for each connection. _idMap maps IDs to
  130. * ReplyHandlers. _handlerMap maps ReplyHandler to IDs. So to clean up a
  131. * connection, we just purge the entries from _handlerMap and _idMap; there
  132. * is no need to iterate through the entire GUID mapping. Adding GUIDs and
  133. * routing replies are still constant-time operations.
  134. *
  135. * IDs are allocated sequentially according with the nextID variable. The
  136. * field does "wrap around" after reaching the maximum integer value.
  137. * Though no two open connections will have the same ID--we check
  138. * _idMap--there is a very low probability that an ID in _map could be
  139. * prematurely reused.
  140. *
  141. * To approximate FIFO behavior, we keep two sets around, _newMap and
  142. * _oldMap. Every few seconds, when the system time is greater than
  143. * nextSwitch, we clear _oldMap and replace it with _newMap.
  144. * (DuplicateFilter uses the same trick.) In this way, we remember the last
  145. * N to 2N minutes worth of GUIDs. This is superior to a fixed size route
  146. * table.
  147. *
  148. * For flow-control reasons, we also store the number of bytes routed per
  149. * GUID in each table. Hence the RouteTableEntry class.
  150. *
  151. * INVARIANT: keys of _newMap and _oldMap are disjoint
  152. * INVARIANT: _idMap and _replyMap are inverses
  153. *
  154. * TODO3: if IDs were stored in each ReplyHandler, we would not need
  155. * _replyMap. Better yet, if the values of _map were indices (with tags)
  156. * into ConnectionManager._initialized[Client]Connections, we would not
  157. * need _idMap either. However, this increases dependenceies.
  158. */
  159. public final class RouteTable {
  160. /*
  161. * RouteTable uses _newMap and _oldMap together.
  162. *
  163. * Both map byte[] to RouteTableEntry.
  164. * The keys are GUID values in byte arrays.
  165. * The values are RouteTableEntry objects.
  166. *
  167. * The maps are Java TreeMap objects that keep their contents in sorted order.
  168. * The constructor takes our GUID.GUIDByteComparator.compare(Object a, Object b) method and uses it to see which of two GUIDs should be placed first.
  169. */
  170. /** A map of message GUIDs and the ReplyHandler objects that represent the remote computers that sent us packets with them. */
  171. private Map _newMap = new TreeMap(new GUID.GUIDByteComparator());
  172. /** We'll move all the listings from _newMap here before throwing them away, keeping the listings this RouteTable keeps current. */
  173. private Map _oldMap = new TreeMap(new GUID.GUIDByteComparator());
  174. /**
  175. * The number of milliseconds this RouteTable will remember its routing information.
  176. * When you make a new RouteTable, you specify the time interval you want.
  177. *
  178. * The purge() method notices if this much time has passed since the last purge.
  179. * If it has, it moves everything from the new map to the old map.
  180. */
  181. private int _mseconds;
  182. /**
  183. * The time when the purge() method must next shift everything from the new map into the old one, and throw out the contents of the old one.
  184. */
  185. private long _nextSwitchTime;
  186. /**
  187. * If the new map grows to hold this many items, the purge() method will shift its contents to the old map and throw the old contents away.
  188. * When you make a new RouteTable, you specify the maximum size you want.
  189. */
  190. private int _maxSize;
  191. /*
  192. * RouteTable uses _idMap and _handlerMap together.
  193. * Both are Java HashMap objects that map keys to values.
  194. * _idMap maps Integer keys to ReplyHandler values.
  195. * _handlerMap maps ReplyHandler keys to Integer values.
  196. *
  197. * To add a ReplyHandler to the maps, call id = handler2id(handler).
  198. * This is also how you look up a handler that's already there, getting it's ID.
  199. * If you have an ID and want it's handler, use handler = id2handler(id).
  200. * Call removeReplyHandler(ReplyHandler) to remove a ReplyHandler from both maps.
  201. */
  202. /** _idMap maps Integer IDs to ReplyHandler objects. */
  203. private Map _idMap = new HashMap();
  204. /** _handlerMap maps ReplyHandler objects to Integer IDs. */
  205. private Map _handlerMap = new HashMap();
  206. /**
  207. * The next ID number to use in _idMap and _handlerMap.
  208. * Java initializes _nextID to 0, the first ID we use.
  209. */
  210. private int _nextID;
  211. /**
  212. * A RouteTable maps GUIDs to RouteTableEntry objects, which contain ReplyHandler objects and more information.
  213. *
  214. * In a RouteTable, _newMap and _oldMap map message GUIDs to RouteTableEntry objects.
  215. * A RouteTableEntry object has our handler ID, and counts the number of bytes and packets we've routed to it.
  216. * To turn a handler ID into a ReplyHandler, use handler = id2handler(id).
  217. *
  218. * A RouteTableEntry object also has a TTL.
  219. * You can access this TTL with getTTL() and setTTL(ttl).
  220. * (do) How is this used?
  221. *
  222. * RouteTableEntry implements the ResultCounter interface, requiring it to have a getNumResults() method.
  223. */
  224. private static final class RouteTableEntry implements ResultCounter {
  225. /** The ID number that you can look up to get the ReplyHandler. */
  226. private int handlerID;
  227. /** The total size of reply packet data we've routed to the ReplyHandler for this GUID request. */
  228. private int bytesRouted;
  229. /** The total number of packets we've routed to the ReplyHandler for this GUID request. */
  230. private int repliesRouted;
  231. /**
  232. * The ttl associated with this RTE - meaningful only if > 0. (do)
  233. */
  234. private byte ttl = 0;
  235. /**
  236. * Make a new RouteTableEntry object to put it in _newMap or _oldMap.
  237. *
  238. * @param handlerID An ID number this class will use in place of a reference to a RouteTable object
  239. */
  240. RouteTableEntry(int handlerID) {
  241. // Save the given handler ID
  242. this.handlerID = handlerID;
  243. // Start the transfer statistics at 0
  244. this.bytesRouted = 0;
  245. this.repliesRouted = 0;
  246. }
  247. /**
  248. * Set the TTL this RouteTableEntry object will remember.
  249. *
  250. * @param ttl The new TTL number to store in this object
  251. */
  252. public void setTTL(byte ttl) {
  253. // Save the given TTL number
  254. this.ttl = ttl;
  255. }
  256. /**
  257. * Get the TTL you set in this RouteTableEntry object.
  258. *
  259. * @return The TTL code set with setTTL(ttl)
  260. */
  261. public byte getTTL() {
  262. // Return the set number
  263. return ttl;
  264. }
  265. /**
  266. * Get the number of reply packets with this GUID that we've received.
  267. *
  268. * In a RouteTable, message GUIDs map to RouteTableEntry objects.
  269. * The RouteTableEntry counts this number of reply packets, and keeps the ReplyHandler which made the request.
  270. *
  271. * The ResultCounter interface requires this method.
  272. *
  273. * @return The number of reply packets we've routed to this remote computer for the request packet with this GUID
  274. */
  275. public int getNumResults() {
  276. // Return the count we've been keeping
  277. return repliesRouted;
  278. }
  279. }
  280. /**
  281. * Make a new RouteTable that will map message GUIDs to remote computers so we know where to send back a reply packet.
  282. *
  283. * @param seconds The new RouteTable will remember a message GUID for from this long to twice this long
  284. * @param maxSize The new RouteTable will remember this many message GUIDs
  285. */
  286. public RouteTable(int seconds, int maxSize) {
  287. /*
  288. * Typically maxSize is very large, and serves only as a guarantee to
  289. * prevent worst case behavior. Actually 2*maxSize elements can be held in
  290. * this in the worst case.
  291. */
  292. // Save the given values, and set the times
  293. this._mseconds = seconds * 1000; // Convert the given time in seconds to milliseconds before saving it in _mseconds
  294. this._nextSwitchTime = System.currentTimeMillis() + _mseconds; // Let purge() run for the first time that long from now
  295. this._maxSize = maxSize;
  296. }
  297. /**
  298. * Add a new routing entry to this RouteTable, renewing it if it already exits.
  299. * Do this when we get a request packet we're going to broadcast forward.
  300. * Add the packet's GUID and the remote computer that sent it to us to the RouteTable.
  301. * Later, when we get a reply packet with the same GUID, we'll know what computer to send it to.
  302. *
  303. * Calls replyHandler.isOpen() to make sure the ManagedConnection can still send a packet to the remote computer it represents.
  304. * Returns null if it's closed, so it can't.
  305. *
  306. * Removes the GUID from the old or new lists before adding it to the new list.
  307. * This has the effect of renewing the GUID.
  308. *
  309. * @return guid The message GUID of a request packet we received and are going to broadcast forward.
  310. * @return replyHandler The ManagedConnection, UDPReplyHandler, or ForMeReplyHandler that sent it to us.
  311. * @return A RouteTableEntry object that contains the given ReplyHandler, and is now listed in this RouteTable under the given GUID.
  312. * RouteTableEntry implements the ResultCounter interface, letting you call getNumResults() on it.
  313. * If the given ReplyHandler is a ManagedConnection that's closed, doesn't add anything and returns null.
  314. */
  315. public synchronized ResultCounter routeReply(byte[] guid, ReplyHandler replyHandler) {
  316. // Used for testing
  317. repOk();
  318. // Discard the old records from this RouteTable
  319. purge();
  320. // Make sure the caller gave us a ReplyHandler object that will be able to send a reply packet to the remote computer it represents
  321. if (replyHandler == null) throw new NullPointerException("null reply handler");
  322. if (!replyHandler.isOpen()) return null; // Make sure it can still send a packet to the computer it represents
  323. /*
  324. * First clear out any old entries for the guid, memorizing the volume
  325. * routed if found. Note that if the guid is found in _newMap, we don't
  326. * need to look in _oldMap.
  327. */
  328. // Look up or add and look up the given ReplyHandler, getting or assigning and getting our ID for it
  329. int id = handler2id(replyHandler).intValue();
  330. // Remove the given GUID from this RouteTable
  331. RouteTableEntry entry = (RouteTableEntry)_newMap.remove(guid);
  332. if (entry == null) entry = (RouteTableEntry)_oldMap.remove(guid); // We only need to remove it from _oldMap if it's not in _newMap
  333. /*
  334. * Now map the guid to the new reply handler, using the volume routed if
  335. * found, or zero otherwise.
  336. */
  337. // This RouteTable doesn't have an entry for the given GUID
  338. if (entry == null) {
  339. // Make a new RouteTableEntry for the given ReplyHandler, we'll store it under the given GUID
  340. entry = new RouteTableEntry(id); // The RouteTableEntry object will hold the ID that leads to the ReplyHandler, not the ReplyHandler itself
  341. // We found the GUID in this RouteTable
  342. } else {
  343. // Point the existing RouteTableEntry at the given ReplyHandler
  344. entry.handlerID = id; // This saved us from having to make a new RouteTableEntry object
  345. }
  346. // Add the given ReplyHandler under the given GUID in the new map, and return the RouteTableEntry that contains it
  347. _newMap.put(guid, entry);
  348. return entry;
  349. }
  350. /**
  351. * Add a new routing entry to this RouteTable, not doing anything it if it already exits.
  352. * Do this when we get a request packet we're going to broadcast forward.
  353. * Add the packet's GUID and the remote computer that sent it to us to the RouteTable.
  354. * Later, when we get a reply packet with the same GUID, we'll know what computer to send it to.
  355. *
  356. * Calls replyHandler.isOpen() to make sure the ManagedConnection can still send a packet to the remote computer it represents.
  357. * Returns null if it's closed, so it can't.
  358. *
  359. * If the GUID already has an entry in the old or new lists, doesn't renew it, just returns null.
  360. *
  361. * @return guid The message GUID of a request packet we received and are going to broadcast forward.
  362. * @return replyHandler The ManagedConnection, UDPReplyHandler, or ForMeReplyHandler that sent it to us.
  363. * @return A RouteTableEntry object that contains the given ReplyHandler, and is now listed in this RouteTable under the given GUID.
  364. * RouteTableEntry implements the ResultCounter interface, letting you call getNumResults() on it.
  365. * If the given ReplyHandler is a ManagedConnection that's closed, doesn't add anything and returns null.
  366. * If the given GUID is already listed in this RouteTable, doesn't renew it and returns null.
  367. */
  368. public synchronized ResultCounter tryToRouteReply(byte[] guid, ReplyHandler replyHandler) {
  369. // Used for testing
  370. repOk();
  371. // Discard the old records from this RouteTable
  372. purge();
  373. // Make sure the caller gave us a ReplyHandler object that will be able to send a reply packet to the remote computer it represents, and a GUID to list it under
  374. Assert.that(replyHandler != null);
  375. Assert.that(guid != null, "Null GUID in tryToRouteReply");
  376. if (!replyHandler.isOpen()) return null; // Return null if the given ReplyHandler is a ManagedConnection that's lost its TCP socket connection
  377. // The GUID isn't in the new or old lists yet
  378. if (!_newMap.containsKey(guid) && !_oldMap.containsKey(guid)) {
  379. // Add the given ReplyHandler under the given GUID in the new map, and return the RouteTableEntry that contains it
  380. int id = handler2id(replyHandler).intValue(); // Assign a new ID for the given ReplyHandler
  381. RouteTableEntry entry = new RouteTableEntry(id); // Make a new RouteTableEntry that will lead to the given ReplyHandler by containing this ID
  382. _newMap.put(guid, entry); // Add the RouteTableEntry to the new map under the GUID key
  383. return entry; // Return it as a ResultCounter you can call getNumResults() on
  384. // We already have this GUID
  385. } else {
  386. // Return null, call routeReply() to renew an already listed GUID
  387. return null;
  388. }
  389. }
  390. /**
  391. * Set the TTL that an entry in a RouteTable for a GUID keeps.
  392. *
  393. * A RouteTableEntry, which is a ResultCounter, contains a ReplyHandler and a TTL.
  394. * Code in this class doesn't change or read the TTL, it just keeps it here.
  395. *
  396. * Optional operation - if you want to remember the TTL associated with a
  397. * counter, in order to allow for extendable execution, you can set the TTL
  398. * a message (guid).
  399. *
  400. * @param entry A RouteTableEntry stored under a GUID in this RouteTable.
  401. * RouteTableEntry implements ResultCounter, so you may have a reference of that type.
  402. * @param ttl The TTL to store in a GUID's entry.
  403. * This should be greater than 0.
  404. */
  405. public synchronized void setTTL(ResultCounter entry, byte ttl) {
  406. // Make sure the caller gave us a RouteTableEntry object, and the TTL isn't 0
  407. if (entry == null) throw new IllegalArgumentException("Null entry!!");
  408. if (!(entry instanceof RouteTableEntry)) throw new IllegalArgumentException("entry is not recognized.");
  409. if (!(ttl > 0)) throw new IllegalArgumentException("Input TTL too small: " + ttl);
  410. // Have the RouteTableEntry object remember the given TTL
  411. ((RouteTableEntry)entry).setTTL(ttl);
  412. }
  413. /**
  414. * Change the TTL stored under a GUID in this RouteTable if you know it's current value.
  415. * Only MessageRouter.wasProbeQuery() calls this.
  416. *
  417. * Synchronizes a TTL get test with a set test.
  418. *
  419. * @param guid A GUID that should be listed in this RouteTable.
  420. * @param getTTL The TTL we think it is remembering.
  421. * @param setTTL We want to change from that TTL to this one.
  422. * @return True if the GUID was listed here with getTTL, and we changed it to setTTL.
  423. * False if the GUID isn't here, or doesn't have getTTL.
  424. */
  425. public synchronized boolean getAndSetTTL(byte[] guid, byte getTTL, byte setTTL) {
  426. // Make sure both TTLs are 1 or more, and getTTL is the same or bigger than setTTL
  427. if ((getTTL < 1) || (setTTL <= getTTL)) throw new IllegalArgumentException("Bad ttl input (get/set): " + getTTL + "/" + setTTL);
  428. // Look up the given GUID in this RouteTable
  429. RouteTableEntry entry = (RouteTableEntry)_newMap.get(guid);
  430. if (entry == null) entry = (RouteTableEntry)_oldMap.get(guid);
  431. // If we have the GUID listed and the TTL it's remembering is the same as getTTL
  432. if ((entry != null) && (entry.getTTL() == getTTL)) {
  433. // Have it remember the new TTL instead, and return true
  434. entry.setTTL(setTTL);
  435. return true;
  436. }
  437. // The GUID isn't listed, or doesn't have getTTL
  438. return false;
  439. }
  440. /**
  441. * Given a message GUID, look up the ReplyHandler object that represents the remote computer that sent us the request packet and should get the reply packet.
  442. * A ReplyHandler is a ManagedConnection or UDPReplyHandler object that represents a remote computer and can send a packet to it.
  443. *
  444. * Doesn't purge the lists before using them like other methods here do.
  445. *
  446. * @param guid The GUID of a reply message that we may have seen in a request packet before.
  447. * @return The ReplyHandler object that represents our connection to the remote computer that sent us a request packet with that message GUID.
  448. * null if not found.
  449. */
  450. public synchronized ReplyHandler getReplyHandler(byte[] guid) {
  451. /*
  452. * no purge
  453. */
  454. // Used for testing
  455. repOk();
  456. // Look up the given message GUID to find the ID we assigned the desired ReplyHandler
  457. RouteTableEntry entry = (RouteTableEntry)_newMap.get(guid); // Look for it in _newMap
  458. if (entry == null) entry = (RouteTableEntry)_oldMap.get(guid); // If not there, look for it in _oldMap
  459. // Look up the ID to get the ReplyHandler, and return it
  460. return (entry == null) ? null : id2handler(new Integer(entry.handlerID)); // Returns null if not found
  461. }
  462. /**
  463. * Given a message GUID, look up the ReplyHandler object that represents the remote computer that sent us the request packet and should get the reply packet.
  464. * A ReplyHandler is a ManagedConnection or UDPReplyHandler object that represents a remote computer and can send a packet to it.
  465. *
  466. * This getReplyHandler() method returns a ReplyRoutePair object, not a ReplyHandler.
  467. * The ReplyRoutePair contains the ReplyHandler, along with the number of packets and size of packet data we've sent in reply.
  468. *
  469. * You can pass this method two more pieces of information, a byte size and a packet count.
  470. * They let you record that you've sent back that many more bytes of reply packet data and that many more reply packets because of this request.
  471. * getReplyHandler() returns the saved statistics, and then increments them.
  472. *
  473. * Doesn't purge the lists before using them like other methods here do.
  474. *
  475. * @param guid The GUID of a reply message that we may have seen in a request packet before.
  476. * @param replyBytes The number of additional bytes of reply packet data we're routing back because of the request.
  477. * @param numReplies The number of additional file hit blocks in the packets we're routing back because of the request.
  478. * @return A ReplyRoutePair object that contains the ReplyHandler that represents our connection to that computer, and packet statistics.
  479. * null if not found.
  480. */
  481. public synchronized ReplyRoutePair getReplyHandler(byte[] guid, int replyBytes, short numReplies) {
  482. /*
  483. * no purge
  484. */
  485. // Used for testing
  486. repOk();
  487. // Look up the given message GUID to find the RouteTableEntry that contains the ReplyHandler that represents the remote computer that sent us a request packet with that message GUID
  488. RouteTableEntry entry = (RouteTableEntry)_newMap.get(guid); // Look for it in _newMap
  489. if (entry == null) entry = (RouteTableEntry)_oldMap.get(guid); // If not there, look for it in _oldMap
  490. if (entry == null) return null; // Not found
  491. // Look up the ID to get the ReplyHandler
  492. ReplyHandler handler = id2handler(new Integer(entry.handlerID));
  493. if (handler == null) return null; // Not found
  494. /*
  495. * Increment count, returning old count in tuple.
  496. */
  497. // Make a new ReplyRoutePair object to match the RouteTableEntry object for the given GUID
  498. ReplyRoutePair ret = new ReplyRoutePair(
  499. handler, // From the GUID, we found the ID and then the ReplyHandler
  500. entry.bytesRouted, // Save the RouteTableEntry's bytesRouted field in ReplyRoutePair.volume
  501. entry.repliesRouted); // Save the RouteTableEntry's repliesRouted field in ReplyRoutePair.REPLIES_ROUTED
  502. // Add the new bytes and file hit blocks we sent to the RouteTableEntry in the new or old map, leaving the ReplyRoutePair we just made unchanged
  503. entry.bytesRouted += replyBytes;
  504. entry.repliesRouted += numReplies;
  505. // Return the ReplyRoutePair with the found ReplyHandler and the packet statistics before the current updates
  506. return ret;
  507. }
  508. /**
  509. * A ReplyRoutePair object keeps a ReplyHandler with transfer statistics.
  510. * A ReplyHandler is a ManagedConnection or UDPReplyHandler object that represents a remote computer and can send a reply packet to it.
  511. * The statistics are volume and REPLIES_ROUTED.
  512. * REPLIES_ROUTED is the number of reply packets its sent, and volume is their total size in bytes.
  513. *
  514. * The getReplyHandler() method above returns a ReplyRoutePair object, not a ReplyHandler.
  515. * This lets it return the statistics along with the ReplyHandler.
  516. */
  517. public static final class ReplyRoutePair {
  518. /** A MangedConnection or UDPReplyHandler object that represents a remote computer and can send a packet to it. */
  519. private final ReplyHandler handler;
  520. /** The number of bytes of packet data the ReplyHandler object has sent its computer because of this GUID request. */
  521. private final int volume;
  522. /** The number of file hits the ReplyHandler object has sent its computer because of this GUID request. */
  523. private final int REPLIES_ROUTED;
  524. /**
  525. * Make a new ReplyRoutePair object given information from a RouteTableEntry.
  526. *
  527. * @param handler The ReplyHandler, the ManagedConnection or UDPReplyHandler object that can send a reply packet to the remote computer it represents
  528. * @param volume The total size of all the reply packets it's sent its remote computer because of this GUID request
  529. * @param hits The number of file hits it's sent its remote computer because of this GUID request
  530. */
  531. ReplyRoutePair(ReplyHandler handler, int volume, int hits) {
  532. // Save the ReplyHandler in this object with its statistics
  533. this.handler = handler;
  534. this.volume = volume;
  535. REPLIES_ROUTED = hits;
  536. }
  537. /**
  538. * Get the ReplyHandler stored in this object.
  539. * A ReplyHandler is a ManagedConnection or UDPReplyHandler object that represents a remote computer and can send a packet to it.
  540. * This ReplyRoutePair object keeps a ReplyHandler together with some statistics about how many packets have gone to it because of a GUID request.
  541. *
  542. * @return The ReplyHandler object inside this ReplyRoutePair
  543. */
  544. public ReplyHandler getReplyHandler() {
  545. // Return the reference the constructor saved
  546. return handler;
  547. }
  548. /**
  549. * The number of bytes of packet data the ReplyHandler in this ReplyRoutePair object has sent its computer because of this GUID request.
  550. *
  551. * @return The data size in bytes
  552. */
  553. public int getBytesRouted() {
  554. // Return the number the constructor saved
  555. return volume;
  556. }
  557. /**
  558. * The number of packets the ReplyHandler in this ReplyRoutePair object has sent its computer because of this GUID request.
  559. *
  560. * @return The number of packets
  561. */
  562. public int getResultsRouted() {
  563. // Return the number the constructor saved
  564. return REPLIES_ROUTED;
  565. }
  566. }
  567. /**
  568. * Remove a ReplyHandler and its given ID from _idMap and _handlerMap.
  569. * Doesn't purge the lists before using them like other methods here do.
  570. * Doesn't report an error if not found.
  571. *
  572. * @param replyHandler A ReplyHandler object to remove from the _idMap and _handlerMap lists
  573. */
  574. public synchronized void removeReplyHandler(ReplyHandler replyHandler) {
  575. /*
  576. * no purge
  577. */
  578. // Used for testing
  579. repOk();
  580. /*
  581. * The aggressive asserts below are to make sure bug X75 has been fixed.
  582. */
  583. // Make sure the caller actually gave us a ReplyHandler to remove
  584. Assert.that(replyHandler != null, "Null replyHandler in removeReplyHandler");
  585. /*
  586. * Note that _map is not modified. See overview of class for rationale.
  587. * Also, handler2id may actually allocate a new ID for replyHandler, when
  588. * killing a connection for which we've routed no replies. That's ok;
  589. * we'll just clean up the new ID immediately.
  590. */
  591. // Get the ReplyHandler's ID
  592. Integer id = handler2id(replyHandler); // If not found, this will add it and assign it an ID
  593. // Remove the ReplyHandler and its ID from the _idMap and _handlerMap
  594. _idMap.remove(id);
  595. _handlerMap.remove(replyHandler);
  596. }
  597. /**
  598. * Look up a ReplyHandler in the _handlerMap and _idMap, and get its ID.
  599. * If not found, adds the ReplyHandler to the lists, and returns the new ID we assigned it.
  600. *
  601. * If the handler is already in _handlerMap and _idMap, returns its ID.
  602. * If it's not found, adds it.
  603. * Chooses a new ID that isn't being used.
  604. * Adds the handler and ID to _handlerMap and _idMap.
  605. * Returns the new ID.
  606. *
  607. * @param handler A ReplyHandler to find or add in the lists.
  608. * @return An Integer object, the associated ID we found or assigned.
  609. */
  610. private Integer handler2id(ReplyHandler handler) {
  611. /*
  612. * Have we encountered this handler recently? If so, return the id.
  613. */
  614. // Look up the given handler in _handlerMap, and if found, return its ID value
  615. Integer id = (Integer)_handlerMap.get(handler);
  616. if (id != null) return id;
  617. /*
  618. * Otherwise return the next free id, searching in extremely rare cases
  619. * if needed. Note that his enters an infinite loop if all 2^32 IDs are
  620. * taken up. BFD.
  621. */
  622. // Loop to search for an ID that's not being used
  623. while (true) {
  624. /*
  625. * don't worry about overflow; Java wraps around TODO1?
  626. */
  627. // Loop until we find an ID number that isn't being used as a key in the _idMap list
  628. id = new Integer(_nextID++); // Get the current value of _nextID, wrap it into an Integer object, and then move _nextID to the next higher value for next time
  629. if (_idMap.get(id) == null) break; // If looking up that ID finds nothing, leave the loop to use it as the new key
  630. }
  631. // Load the given ReplyHandler and newly chosen ID into the _handlerMap and _idMap lists, and return the ID
  632. _handlerMap.put(handler, id); // Use _handlerMap if you know the handler, and want the ID
  633. _idMap.put(id, handler); // Use _idMap if you know the ID, and want the handler
  634. return id; // Return the ID we chose for the given hander
  635. }
  636. /**
  637. * Look up the ReplyHandler stored under the given ID key in _idMap.
  638. *
  639. * @param id An ID number.
  640. * @return The ReplyHandler stored under that key in _idMap.
  641. * null if not found.
  642. */
  643. private ReplyHandler id2handler(Integer id) {
  644. // Look up the ID in _idMap, cast the value back to a ReplyHandler, and return it
  645. return (ReplyHandler)_idMap.get(id);
  646. }
  647. /**
  648. * Shifts the contents of _newMap into _oldMap, and throws the old contents away.
  649. * Only does this if it's been _mseconds since the last switch, or _newMap has grown too large.
  650. *
  651. * @return True if the time expired or the new map grew too large, and we shifted the list contents.
  652. * False if we still need to wait some more.
  653. */
  654. private final boolean purge() {
  655. // Only do something if we've waited long enough or the new map has grown too large
  656. long now = System.currentTimeMillis(); // Get the time right now
  657. if (now < _nextSwitchTime && // We haven't waited long enough yet, and
  658. _newMap.size() < _maxSize) // The new map hasn't overgrown yet
  659. return false; // Come back later
  660. /*
  661. * System.out.println(now + " " + this.hashCode() + " purging " + _oldMap.size() + " old, " + _newMap.size() + " new");
  662. */
  663. // Clear the old map, and then move everything from the new map into it
  664. _oldMap.clear(); // Delete the contents of the old map
  665. Map tmp = _oldMap; // Point tmp at the old map, which is empty
  666. _oldMap = _newMap; // Point _oldMap at the new map, which still has contents
  667. _newMap = tmp; // Point _newMap at tmp, our reference to the empty old map
  668. // Set the next time we'll do this
  669. _nextSwitchTime = now + _mseconds;
  670. // Report that we purged the lists
  671. return true;
  672. }
  673. /**
  674. * Express this RouteTable object as a string.
  675. * Composes text like: "{GGGGUUUUIIIIDDDD->CONNECTION: host=1.2.3.4 port=6346, GGGGUUUUIIIIDDDD->... }".
  676. * Calls ManagedConnection.toString(), UDPReplyHandler.toString(), and ForMeReplyHandler.toString() to have each ReplyHandler describe itself.
  677. *
  678. * @return A String
  679. */
  680. public synchronized String toString() {
  681. /*
  682. * Inefficient, but this is only for debugging anyway.
  683. */
  684. // Make a StringBuffer that can grow to hold text, and start with a "{"
  685. StringBuffer buf = new StringBuffer("{");
  686. // Combine the _oldMap and _newMap lists into a single big one
  687. Map bothMaps = new TreeMap(new GUID.GUIDByteComparator()); // Have it use our GUID.GUIDByteComparator.compare(Object a, Object b) to keep the contents sorted
  688. bothMaps.putAll(_oldMap); // Add everything in both the old and new maps to it
  689. bothMaps.putAll(_newMap);
  690. // Loop through the combined list we just made
  691. Iterator iter = bothMaps.keySet().iterator();
  692. while (iter.hasNext()) {
  693. byte[] key = (byte[])iter.next(); // Get the key the iterator is on, the key is a byte array of 16 bytes holding a GUID value
  694. buf.append(new GUID(key)); // Make the 16 bytes into a GUID object, convert that into base 16 text, and add it to the text we're composing
  695. buf.append("->"); // After the GUID, write an arrow
  696. int id = ((RouteTableEntry)bothMaps.get(key)).handlerID; // Look up the byte array key in the combined map we made, get its RouteTableEntry value, and read its handler ID
  697. ReplyHandler handler = id2handler(new Integer(id)); // Look up that ID in the _idMap to get the ReplyHandler it's for
  698. buf.append(handler == null ? "null" : handler.toString()); // Have the ReplyHandler express itself in text, calls ManagedConnection.toString(), UDPReplyHandler.toString(), or ForMeReplyHandler.toString()
  699. if (iter.hasNext()) buf.append(", ");
  700. }
  701. // End the text with the closing "}", and return it
  702. buf.append("}");
  703. return buf.toString();
  704. }
  705. /**
  706. * Not used.
  707. *
  708. * Used in the commented code in repOk().
  709. */
  710. private static boolean warned = false;
  711. /**
  712. * Does nothing.
  713. *
  714. * Would look at the lists this RouteTable keeps to check that everything looks correct.
  715. * Very slow.
  716. */
  717. private final void repOk() {
  718. /*
  719. if (!warned) {
  720. System.err.println(
  721. "WARNING: RouteTable.repOk enabled. Expect performance problems!");
  722. warned=true;
  723. }
  724. //Check that _idMap is inverse of _handlerMap...
  725. for (Iterator iter=_idMap.keySet().iterator(); iter.hasNext(); ) {
  726. Integer key=(Integer)iter.next();
  727. ReplyHandler value=(ReplyHandler)_idMap.get(key);
  728. Assert.that(_handlerMap.get(value)==key);
  729. }
  730. //..and vice versa
  731. for (Iterator iter=_handlerMap.keySet().iterator(); iter.hasNext(); ) {
  732. ReplyHandler key=(ReplyHandler)iter.next();
  733. Integer value=(Integer)_handlerMap.get(key);
  734. Assert.that(_idMap.get(value)==key);
  735. }
  736. //Check that keys of _newMap aren't in _oldMap, values are RouteTableEntry
  737. for (Iterator iter=_newMap.keySet().iterator(); iter.hasNext(); ) {
  738. byte[] guid=(byte[])iter.next();
  739. Assert.that(! _oldMap.containsKey(guid));
  740. Assert.that(_newMap.get(guid) instanceof RouteTableEntry);
  741. }
  742. //Check that keys of _oldMap aren't in _newMap
  743. for (Iterator iter=_oldMap.keySet().iterator(); iter.hasNext(); ) {
  744. byte[] guid=(byte[])iter.next();
  745. Assert.that(! _newMap.containsKey(guid));
  746. Assert.that(_oldMap.get(guid) instanceof RouteTableEntry);
  747. }
  748. */
  749. }
  750. }