/* * Copyright 2013 Thomas Bocek * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package net.tomp2p.peers; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.SortedSet; import java.util.TreeSet; import net.tomp2p.connection.PeerConnection; import net.tomp2p.connection.PeerException; import net.tomp2p.connection.PeerException.AbortCause; import net.tomp2p.p2p.PeerStatisticComparator; import net.tomp2p.utils.CacheMap; import net.tomp2p.utils.ConcurrentCacheMap; import net.tomp2p.utils.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This routing implementation uses is based on Kademlia. However, many changes have been applied to make it faster and * more flexible. This class is partially thread-safe. * * @author Thomas Bocek */ public class PeerMap implements PeerStatusListener, Maintainable { private static final Logger LOG = LoggerFactory.getLogger(PeerMap.class); // each distance bit has its own bag this is the size of the verified peers (the ones that we know are reachable) private final int[] bagSizesVerified; private final int[] bagSizesOverflow; // the id of this node private final Number160 self; // the storage for the peers that are verified private final List<Map<Number160, PeerStatistic>> peerMapVerified; // the storage for the peers that are not verified or overflown private final List<Map<Number160, PeerStatistic>> peerMapOverflow; private final ConcurrentCacheMap<Number160, PeerAddress> offlineMap; private final ConcurrentCacheMap<Number160, PeerAddress> shutdownMap; private final ConcurrentCacheMap<Number160, PeerAddress> exceptionMap; // stores listeners that will be notified if a peer gets removed or added private final List<PeerMapChangeListener> peerMapChangeListeners = new ArrayList<PeerMapChangeListener>(); private final Collection<PeerMapFilter> peerMapFilters; // the number of failures until a peer is considered offline private final int offlineCount; private final Maintenance maintenance; private final boolean peerVerification; private PeerStatisticComparator peerStatisticComparator; /** * Creates the bag for the peers. This peer knows a lot about close peers and the further away the peers are, the * less known they are. Distance is measured with XOR of the peer ID. The distance of peer with ID 0x12 and peer * with Id 0x28 is 0x3a. * * @param peerMapConfiguration * The configuration values of this map * */ public PeerMap(final PeerMapConfiguration peerMapConfiguration) { this.self = peerMapConfiguration.self(); if (self == null || self.isZero()) { throw new IllegalArgumentException("Zero or null are not a valid IDs"); } this.bagSizesVerified = peerMapConfiguration.getVerifiedBagSizes(); this.bagSizesOverflow = peerMapConfiguration.getOverflowBagSizes(); this.offlineCount = peerMapConfiguration.offlineCount(); this.peerMapFilters = peerMapConfiguration.peerMapFilters(); this.peerMapVerified = initMap(bagSizesVerified, false); this.peerMapOverflow = initMap(bagSizesOverflow, true); // bagSizeVerified * Number160.BITS should be enough this.offlineMap = new ConcurrentCacheMap<Number160, PeerAddress>( peerMapConfiguration.offlineTimeout(), totalNumberOfVerifiedBags()); this.shutdownMap = new ConcurrentCacheMap<Number160, PeerAddress>( peerMapConfiguration.shutdownTimeout(), totalNumberOfVerifiedBags()); this.exceptionMap = new ConcurrentCacheMap<Number160, PeerAddress>( peerMapConfiguration.exceptionTimeout(), totalNumberOfVerifiedBags()); this.maintenance = peerMapConfiguration.maintenance().init(peerMapVerified, peerMapOverflow, offlineMap, shutdownMap, exceptionMap); this.peerVerification = peerMapConfiguration.isPeerVerification(); this.peerStatisticComparator = peerMapConfiguration.getPeerStatisticComparator(); } private int totalNumberOfVerifiedBags() { int sum = 0; for (int i=0; i<Number160.BITS; i++) { sum += bagSizesVerified[i]; } return sum; } /** * Create a list of bag with an unmodifiable map. * * @param bagSizes * The bag sizes * @param caching * If a caching map should be created * * @return The list of bags containing an unmodifiable map */ private List<Map<Number160, PeerStatistic>> initMap(final int[] bagSizes, final boolean caching) { List<Map<Number160, PeerStatistic>> tmp = new ArrayList<Map<Number160, PeerStatistic>>(); for (int i = 0; i < Number160.BITS; i++) { // I made some experiments here and concurrent sets are not // necessary, as we divide similar to segments aNonBlockingHashSets // in a concurrent map. In a full network, we have 160 segments, for // smaller we see around 3-4 segments, growing with the number of // peers. bags closer to 0 will see more read than write, and bags // closer to 160 will see more writes than reads. // // We also only allocate memory for the bags far away, as they are likely to be filled first. if (caching) { tmp.add(new CacheMap<Number160, PeerStatistic>(bagSizes[i], true)); } else { final int memAlloc = bagSizes[i] / 8; tmp.add(new HashMap<Number160, PeerStatistic>(memAlloc)); } } return Collections.unmodifiableList(tmp); } /** * Adds a map change listener. This is thread-safe * * @param peerMapChangeListener * The listener */ public void addPeerMapChangeListener(final PeerMapChangeListener peerMapChangeListener) { synchronized (peerMapChangeListeners) { peerMapChangeListeners.add(peerMapChangeListener); } } /** * Removes a map change listener. This is thread-safe * * @param peerMapChangeListener * The listener */ public void removePeerMapChangeListener(final PeerMapChangeListener peerMapChangeListener) { synchronized (peerMapChangeListeners) { peerMapChangeListeners.remove(peerMapChangeListener); } } /** * Notifies on insert. This is called after the peer has been added to the map. * * @param peerAddress * The address of the inserted peer * @param verified * True if the peer was inserted into the verified map */ private void notifyInsert(final PeerAddress peerAddress, final boolean verified) { synchronized (peerMapChangeListeners) { for (PeerMapChangeListener listener : peerMapChangeListeners) { listener.peerInserted(peerAddress, verified); } } } /** * Notifies on remove. This method is thread safe. * * @param peerAddress * The address of the removed peer * @param storedPeerAddress * Contains statistical information */ private void notifyRemove(final PeerAddress peerAddress, final PeerStatistic storedPeerAddress) { synchronized (peerMapChangeListeners) { for (PeerMapChangeListener listener : peerMapChangeListeners) { listener.peerRemoved(peerAddress, storedPeerAddress); } } } /** * Notifies on update. This method is thread safe. * * @param peerAddress * The address of the updated peer. * @param storedPeerAddress * Contains statistical information */ private void notifyUpdate(final PeerAddress peerAddress, final PeerStatistic storedPeerAddress) { synchronized (peerMapChangeListeners) { for (PeerMapChangeListener listener : peerMapChangeListeners) { listener.peerUpdated(peerAddress, storedPeerAddress); } } } /** * The number of the peers in the verified map. * * @return the total number of peers */ public int size() { int size = 0; for (Map<Number160, PeerStatistic> map : peerMapVerified) { synchronized (map) { size += map.size(); } } return size; } /** * Each node that has a bag has an ID itself to define what is close. This method returns this ID. * * @return The id of this node */ public Number160 self() { return self; } private boolean reject (PeerAddress peerAddress) { if(peerMapFilters == null || peerMapFilters.size() ==0) { return false; } for(PeerMapFilter peerFilter:peerMapFilters) { if(peerFilter.rejectPeerMap(peerAddress, this)) { return true; } } return false; } /** * Adds a neighbor to the neighbor list. If the bag is full, the id zero or the same as our id, the neighbor is not * added. This method is tread-safe * * @param remotePeer * The node to be added * @param referrer * If we had direct contact and we know for sure that this node is online, we set firsthand to true. * Information from 3rd party peers are always second hand and treated as such * @param roundTripTime * A RTT object, that measured the round-trip-time related to the finding of this peer. This is * will be saved into the PeerStatistic * @return True if the neighbor could be added or updated, otherwise false. */ @Override public boolean peerFound(PeerAddress remotePeer, final PeerAddress referrer, final PeerConnection peerConnection, RTT roundTripTime) { LOG.debug("Peer {} is online. Reporter was {}.", remotePeer, referrer); boolean firstHand = referrer == null; //if we got contacted by this peer, but we did not initiate the connection boolean secondHand = remotePeer.equals(referrer); //if a peer reported about other peers boolean thirdHand = !firstHand && !secondHand; // always trust first hand information if (firstHand) { offlineMap.remove(remotePeer.peerId()); shutdownMap.remove(remotePeer.peerId()); } if (secondHand && !peerVerification) { offlineMap.remove(remotePeer.peerId()); shutdownMap.remove(remotePeer.peerId()); } // don't add nodes with zero node id, do not add myself and do not add // nodes marked as bad if (remotePeer.peerId().isZero() || self().equals(remotePeer.peerId()) || reject(remotePeer)) { LOG.debug("peer Id is zero, self address, or simply rejected"); return false; } //if we have first hand information, that means we send a message to that peer and we received a reply. //So its not firewalled. This happens in the discovery phase if (remotePeer.unreachable()) { if(firstHand) { remotePeer = remotePeer.withUnreachable(false); } else { LOG.debug("peer is unreachable, reject"); return false; } } final boolean probablyDead = offlineMap.containsKey(remotePeer.peerId()) || shutdownMap.containsKey(remotePeer.peerId()) || exceptionMap.containsKey(remotePeer.peerId()); // don't include secondHand as if we are contacted by an assumed offline // peer and we see the peer is there, assume the peer is not dead. if(thirdHand && probablyDead) { LOG.debug("Most likely offline, reject"); return false; } final int classMember = classMember(remotePeer.peerId()); // Update existing PeerStatistic with RTT info and potential new port final Pair<PeerStatistic,Boolean> old = updateExistingVerifiedPeerAddress( peerMapVerified.get(classMember), remotePeer, firstHand, roundTripTime); if (old != null && old.element1()) { // we update the peer, so we can exit here and report that we have // updated it. notifyUpdate(remotePeer, old.element0()); LOG.debug("Update peer information"); return true; } else if (old != null && !old.element1()) { LOG.debug("Unreliable information, don't update"); //don't update, as we have second hand information that is not reliabel, we already have it, don't update return false; } else { if (firstHand || (secondHand && !peerVerification)) { final Map<Number160, PeerStatistic> map = peerMapVerified.get(classMember); boolean inserted = false; synchronized (map) { // check again, now we are synchronized if (map.containsKey(remotePeer.peerId())) { return peerFound(remotePeer, referrer, peerConnection, roundTripTime); } if (map.size() < bagSizesVerified[classMember]) { final PeerStatistic peerStatistic = new PeerStatistic(remotePeer); peerStatistic.successfullyChecked(); peerStatistic.addRTT(roundTripTime); map.put(remotePeer.peerId(), peerStatistic); inserted = true; } } if (inserted) { // if we inserted into the verified map, remove it from the non-verified map final Map<Number160, PeerStatistic> mapOverflow = peerMapOverflow.get(classMember); synchronized (mapOverflow) { mapOverflow.remove(remotePeer.peerId()); } notifyInsert(remotePeer, true); LOG.debug("Peer inserted"); return true; } } } LOG.debug("Not rejected or inserted, add to overflow map"); // if we are here, we did not have this peer, but our verified map was full // check if we have it stored in the non verified map. final Map<Number160, PeerStatistic> mapOverflow = peerMapOverflow.get(classMember); synchronized (mapOverflow) { PeerStatistic peerStatistic = mapOverflow.get(remotePeer.peerId()); if (peerStatistic == null) { peerStatistic = new PeerStatistic(remotePeer); } if (firstHand) { peerStatistic.successfullyChecked(); peerStatistic.addRTT(roundTripTime); } if (thirdHand && roundTripTime != null) { peerStatistic.addRTT(roundTripTime.setEstimated()); } mapOverflow.put(remotePeer.peerId(), peerStatistic); } notifyInsert(remotePeer, false); return true; } /** * Removes a peer from the list. In order to not reappear, the node is put for a certain time in a cache list to keep * the node removed. This method is thread-safe. * * @param remotePeer * The node to be removed * @return True if the neighbor was removed and added to a cache list. False if it has not been removed or is * already in the temporarily removed list. */ @Override public boolean peerFailed(final PeerAddress remotePeer, final PeerException peerException) { LOG.debug("Peer {} is offline with reason {}.", remotePeer, peerException); // TB: ignore ZERO peer Id for the moment, but we should filter for the IP address if (remotePeer.peerId().isZero() || self().equals(remotePeer.peerId())) { return false; } final int classMember = classMember(remotePeer.peerId()); AbortCause reason = peerException.abortCause(); if (reason != AbortCause.TIMEOUT) { if(reason == AbortCause.PROBABLY_OFFLINE) { offlineMap.put(remotePeer.peerId(), remotePeer); } else if(reason == AbortCause.SHUTDOWN) { shutdownMap.put(remotePeer.peerId(), remotePeer); } else { // reason is exception exceptionMap.put(remotePeer.peerId(), remotePeer); } Map<Number160, PeerStatistic> tmp = peerMapOverflow.get(classMember); if (tmp != null) { synchronized (tmp) { tmp.remove(remotePeer.peerId()); } } tmp = peerMapVerified.get(classMember); if (tmp != null) { boolean removed = false; final PeerStatistic peerStatistic; synchronized (tmp) { peerStatistic = tmp.remove(remotePeer.peerId()); if (peerStatistic != null) { removed = true; } } if (removed) { notifyRemove(remotePeer, peerStatistic); return true; } } return false; } // not forced if (updatePeerStatistic(remotePeer, peerMapVerified.get(classMember), offlineCount)) { return peerFailed(remotePeer, new PeerException(AbortCause.PROBABLY_OFFLINE, "Peer failed in verified map.")); } if (updatePeerStatistic(remotePeer, peerMapOverflow.get(classMember), offlineCount)) { return peerFailed(remotePeer, new PeerException(AbortCause.PROBABLY_OFFLINE, "Peer failed in overflow map.")); } return false; } /** * Checks if a peer address is in the verified map. * * @param peerAddress * The peer address to check * @return True, if the peer address is in the verified map */ public boolean contains(final PeerAddress peerAddress) { final int classMember = classMember(peerAddress.peerId()); if (classMember == -1) { // -1 means we searched for ourself and we never are our neighbor return false; } final Map<Number160, PeerStatistic> tmp = peerMapVerified.get(classMember); synchronized (tmp) { return tmp.containsKey(peerAddress.peerId()); } } /** * Checks if a peer address is in the overflow / non-verified map. * * @param peerAddress * The peer address to check * @return True, if the peer address is either in the overflow / non-verified map */ public boolean containsOverflow(final PeerAddress peerAddress) { final int classMember = classMember(peerAddress.peerId()); if (classMember == -1) { // -1 means we searched for ourself and we never are our neighbor return false; } final Map<Number160, PeerStatistic> tmp = peerMapOverflow.get(classMember); synchronized (tmp) { return tmp.containsKey(peerAddress.peerId()); } } /** * Checks if an entry of that peerAddress is available in the verified peer map * or overflow peer map. * @param peerAddress * @return The PeerStatistic or null if peer is not in peer map */ public PeerStatistic getPeerStatistic(final PeerAddress peerAddress) { PeerStatistic peerStatistic; final int classMember = classMember(peerAddress.peerId()); if (classMember == -1) { // -1 means we searched for ourself and we never are our neighbor return null; } // Try to find PeerStatistic in verified Map peerStatistic = peerMapVerified().get(classMember).get(peerAddress.peerId()); // If that failed, look in the overflow map if (peerStatistic == null) { peerStatistic = peerMapOverflow().get(classMember).get(peerAddress.peerId()); } return peerStatistic; } /** * Returns close peers to the peer itself. * @param atLeast The number we want to find at least * @return A sorted set with close peers first in this set. */ public NavigableSet<PeerStatistic> closePeers(final int atLeast) { return closePeers(self, atLeast); } /** * Returns close peer from the set to a given key. This method is tread-safe. You can use the returned set as its a * copy of the actual PeerMap and changes in the return set do not affect PeerMap. * * @param id * The key that should be close to the keys in the map * @param atLeast * The number we want to find at least * @return A sorted set with close peers first in this set. Use set.first() to get the closest peer */ public NavigableSet<PeerStatistic> closePeers(final Number160 id, final int atLeast) { return closePeers(self, id, atLeast, peerMapVerified, peerStatisticComparator.getComparator(id)); } public static NavigableSet<PeerStatistic> closePeers(final Number160 self, final Number160 other, final int atLeast, List<Map<Number160, PeerStatistic>> peerMap, Comparator<PeerStatistic> comparator) { if (comparator == null) comparator = createXORStatisticComparator(other); final NavigableSet<PeerStatistic> set = new TreeSet<PeerStatistic>(comparator); final int classMember = classMember(self, other); // special treatment, as we can start iterating from 0 if (classMember == -1) { for (int j = 0; j < Number160.BITS; j++) { final Map<Number160, PeerStatistic> tmp = peerMap.get(j); if (fillSet(atLeast, set, tmp)) { return set; } } return set; } Map<Number160, PeerStatistic> tmp = peerMap.get(classMember); if (fillSet(atLeast, set, tmp)) { return set; } // in this case we have to go over all the bags that are smaller boolean last = false; for (int i = 0; i < classMember; i++) { tmp = peerMap.get(i); last = fillSet(atLeast, set, tmp); } if (last) { return set; } // in this case we have to go over all the bags that are larger for (int i = classMember + 1; i < Number160.BITS; i++) { tmp = peerMap.get(i); fillSet(atLeast, set, tmp); } return set; } @Override public String toString() { final StringBuilder sb = new StringBuilder("I'm node "); sb.append(self()).append("\n"); for (int i = 0; i < Number160.BITS; i++) { final Map<Number160, PeerStatistic> tmp = peerMapVerified.get(i); synchronized (tmp) { if (tmp.size() > 0) { sb.append("class:").append(i).append("->\n"); for (final PeerStatistic node : tmp.values()) { sb.append("node:").append(node.peerAddress()).append(","); } } } } return sb.toString(); } /** * Creates an XOR comparator based on this peer ID. * * @return The XOR comparator */ public Comparator<PeerAddress> createXORAddressComparator() { return createXORAddressComparator(self); } /** * Return all addresses from the neighbor list. The collection is a copy and it is partially sorted. * * @return All neighbors */ public List<PeerAddress> all() { final List<PeerAddress> all = new ArrayList<PeerAddress>(); for (final Map<Number160, PeerStatistic> map : peerMapVerified) { synchronized (map) { for (PeerStatistic peerStatistic : map.values()) { all.add(peerStatistic.peerAddress()); } } } return all; } /** * * @param nrNeighbors * @param maxBucket not inclusive * @return */ public List<PeerAddress> fromEachBag(final int nrNeighbors, final int maxBucket) { if(nrNeighbors < 0) { return all(); } final List<PeerAddress> fromEachBag = new ArrayList<PeerAddress>(); int bucketCounter = 0; for (final Map<Number160, PeerStatistic> map : peerMapVerified) { if(++bucketCounter > maxBucket) { break; } synchronized (map) { int neighborCounter = 0; for (PeerStatistic peerStatistic : map.values()) { if(++neighborCounter > nrNeighbors) { break; } fromEachBag.add(peerStatistic.peerAddress()); } } } return fromEachBag; } public List<Map<Number160, PeerStatistic>> peerMapVerified() { return peerMapVerified; } public List<Map<Number160, PeerStatistic>> peerMapOverflow() { return peerMapOverflow; } /** * Return all addresses from the overflow / non-verified list. The collection is a copy and it is partially sorted. * * @return All neighbors */ public List<PeerAddress> allOverflow() { List<PeerAddress> all = new ArrayList<PeerAddress>(); for (Map<Number160, PeerStatistic> map : peerMapOverflow) { synchronized (map) { for (PeerStatistic peerStatistic : map.values()) { all.add(peerStatistic.peerAddress()); } } } return all; } /** * Checks if a peer is in the offline map. * * @param peerAddress * The peer address to look for * @return True if the peer is in the offline map, meaning that we consider this peer offline. */ public boolean isPeerRemovedTemporarly(final PeerAddress peerAddress) { return offlineMap.containsKey(peerAddress.peerId()) || shutdownMap.containsKey(peerAddress.peerId()) || exceptionMap.containsKey(peerAddress.peerId()); } /** * Finds the next peer that should have a maintenance check. Returns null if no maintenance is needed at the moment. * It will return the most important peers first. Importance is as follows: The most important peers are the close * ones in the verified peer map. If a certain threshold in a bag is not reached, the unverified becomes important * too. * * @return The next most important peer to check if it is still alive. */ public PeerStatistic nextForMaintenance(Collection<PeerAddress> notInterestedAddresses) { return maintenance.nextForMaintenance(notInterestedAddresses); } /** * Returns the number of the class that this id belongs to. * * @param remoteID * The id to test * @return The number of bits used in the difference. */ private int classMember(final Number160 remoteID) { return classMember(self(), remoteID); } /** * Returns -1 if the first remote node is closer to the key, if the second is closer, then 1 is returned. If both * are equal, 0 is returned * * @param id * The id as a distance reference * @param rn * The peer to test if closer to the id * @param rn2 * The other peer to test if closer to the id * @return -1 if first peer is closer, 1 otherwise, 0 if both are equal */ public static int isKadCloser(final Number160 id, final PeerAddress rn, final PeerAddress rn2) { return distance(id, rn.peerId()).compareTo(distance(id, rn2.peerId())); } public static int isKadCloser(final Number160 id, final Number160 rn, final Number160 rn2) { return distance(id, rn).compareTo(distance(id, rn2)); } /** * Returns -1 if the first remote node is closer to the key, if the secondBITS is closer, then 1 is returned. If * both are equal, 0 is returned * * @param ln The location * @param rn The peer to test if closer to the id * @param rn2 The other peer to test if closer to the id * @return -1 if rn2 is closer to ln (in terms of xor distance bit length), 1 if * rn1 is closer or 0 if they are equal. */ public static int classCloser(final Number160 ln, final PeerAddress rn, final PeerAddress rn2) { Integer d1 = classMember(ln, rn.peerId()); Integer d2 = classMember(ln, rn2.peerId()); return d1.compareTo(d2); } public static Comparator<Number160> createXORNumberComparator(final Number160 location) { return new Comparator<Number160>() { public int compare(final Number160 remotePeer, final Number160 remotePeer2) { return isKadCloser(location, remotePeer, remotePeer2); } }; } /** * Create the Kademlia distance comparator. * * @param location * The id of this peer * @return The XOR comparator */ public static Comparator<PeerAddress> createXORAddressComparator(final Number160 location) { return new Comparator<PeerAddress>() { public int compare(final PeerAddress remotePeer, final PeerAddress remotePeer2) { return isKadCloser(location, remotePeer, remotePeer2); } }; } public static Comparator<PeerStatistic> createXORStatisticComparator(final Number160 location) { return new Comparator<PeerStatistic>() { @Override public int compare(PeerStatistic o1, PeerStatistic o2) { if (o1.peerAddress() != null && o2.peerAddress() != null) { return isKadCloser(location, o1.peerAddress(), o2.peerAddress()); } return 0; } }; } public Comparator<PeerStatistic> createStatisticComparator(final Number160 location) { return peerStatisticComparator.getComparator(location); } /** * Takes a collection of PeerAddress and returns an equivalent collection * of PeerStatistic from the PeerMap. New PeerStatistics are created for peers * that are not yet in the PeerMap * @param peerAddresses * @return */ public Collection<PeerStatistic> getPeerStatistics(Collection<PeerAddress> peerAddresses) { HashSet<PeerStatistic> result = new HashSet<PeerStatistic>(); for (PeerAddress peerAddress : peerAddresses) { PeerStatistic peerStatistic = getPeerStatistic(peerAddress); if (peerStatistic == null) { peerStatistic = new PeerStatistic(peerAddress); } result.add(peerStatistic); } return result; } /** * Returns the difference in terms of bit counts of two ids, minus 1. So two IDs with one bit difference are in the * class 0. * * @param id1 * The first id * @param id2 * The second id * @return The bit difference and -1 if they are equal */ public static int classMember(final Number160 id1, final Number160 id2) { return distance(id1, id2).bitLength() - 1; } /** * The distance metric is the XOR metric. * * @param id1 * The first id * @param id2 * The second id * @return The XOR distance */ static Number160 distance(final Number160 id1, final Number160 id2) { return id1.xor(id2); } /** * Updates the peer statistics and checks if the max failure has been reached. * * @param remotePeer * The remote peer * @param tmp * The bag of where the peer is supposed to be * @param maxFail * The number of max failure until a peer is considered offline * @return True if this peer is considered offline, otherwise false */ private static boolean updatePeerStatistic(final PeerAddress remotePeer, final Map<Number160, PeerStatistic> tmp, final int maxFail) { if (tmp != null) { synchronized (tmp) { PeerStatistic peerStatistic = tmp.get(remotePeer.peerId()); if (peerStatistic != null) { if (peerStatistic.failed() >= maxFail) { return true; } } } } return false; } /** * Checks if a peer already exists in this map and if it does, it will update the entry because the peer address * (e.g. port) may have changed. * * @param tmp * The map where the peer is supposed to be * @param peerAddress * The address of the peer that may have been changed * @param firstHand * True if this peer send and received a message from the remote peer * @param roundTripTime * @return The old peer address if we have updated the peer, null otherwise */ public static Pair<PeerStatistic,Boolean> updateExistingVerifiedPeerAddress( final Map<Number160, PeerStatistic> tmp, final PeerAddress peerAddress, final boolean firstHand, RTT roundTripTime) { synchronized (tmp) { PeerStatistic old = tmp.get(peerAddress.peerId()); if (old != null && firstHand) { old.peerAddress(peerAddress); old.addRTT(roundTripTime); old.successfullyChecked(); old.increaseNumberOfResponses(); return new Pair<PeerStatistic,Boolean>(old, true); } else if (old != null) { //this is second hand information, don't update, as we already have it. return new Pair<PeerStatistic,Boolean>(old, false); } } return null; } /** * Fills the set with peer addresses. Fills it until a limit is reached. However, this is a soft limit, as the bag may * contain close peers in a random manner. * * @param atLeast * The number of addresses we want at least. It does not matter if it is more. * @param set * The set where to store the results * @param tmp * The bag where to take the addresses from * @return True if the desired size has been reached */ private static boolean fillSet(final int atLeast, final SortedSet<PeerStatistic> set, final Map<Number160, PeerStatistic> tmp) { synchronized (tmp) { for (final PeerStatistic peerStatistic : tmp.values()) { set.add(peerStatistic); } } return set.size() >= atLeast; } public int bagSizeVerified(int bag) { return bagSizesVerified[bag]; } public int bagSizeOverflow(int bag) { return bagSizesOverflow[bag]; } public int nrFilledBags() { int counter = 0; for (final Map<Number160, PeerStatistic> map : peerMapVerified) { synchronized (map) { if(map.size() > 0) { counter++; } } } return counter; } }