/* * Created on 11-Jan-2005 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.dht.router.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SystemTime; import com.aelitis.azureus.core.dht.impl.DHTLog; import com.aelitis.azureus.core.dht.router.DHTRouterContact; import com.aelitis.azureus.core.dht.router.DHTRouterContactAttachment; /** * @author parg * */ public class DHTRouterNodeImpl { private DHTRouterImpl router; private int depth; private boolean contains_router_node_id; private List buckets; private List replacements; private DHTRouterNodeImpl left; private DHTRouterNodeImpl right; private long last_lookup_time; protected DHTRouterNodeImpl( DHTRouterImpl _router, int _depth, boolean _contains_router_node_id, List _buckets ) { router = _router; depth = _depth; contains_router_node_id = _contains_router_node_id; buckets = _buckets; } protected int getDepth() { return( depth ); } protected boolean containsRouterNodeID() { return( contains_router_node_id ); } protected DHTRouterNodeImpl getLeft() { return( left ); } protected DHTRouterNodeImpl getRight() { return( right ); } protected void split( DHTRouterNodeImpl new_left, DHTRouterNodeImpl new_right ) { buckets = null; if ( replacements != null ){ Debug.out( "DHTRouterNode: inconsistenct - splitting a node with replacements" ); } left = new_left; right = new_right; } protected List getBuckets() { return( buckets ); } protected List getReplacements() { return( replacements ); } protected void addNode( DHTRouterContactImpl node ) { // MGP: notify that node added to bucket node.setBucketEntry(); router.notifyAdded(node); buckets.add( node ); requestNodeAdd( node, false ); } protected DHTRouterContact addReplacement( DHTRouterContactImpl replacement, int max_rep_per_node ) { if ( max_rep_per_node == 0 ){ return( null ); } // we ping the oldest bucket entry only if we "improve" matters in the replacement boolean try_ping = false; if( replacements == null ){ try_ping = true; replacements = new ArrayList(); }else{ if ( replacements.size() == max_rep_per_node ){ // if this replacement is known to be alive, replace any existing // replacements that haven't been known to be alive if ( replacement.hasBeenAlive() ){ for (int i=0;i<replacements.size();i++){ DHTRouterContactImpl r = (DHTRouterContactImpl)replacements.get(i); if ( !r.hasBeenAlive()){ try_ping = true; // MGP: kicking out a replacement that was never alive router.notifyRemoved(r); replacements.remove(i); break; } } // no unknown existing replacements but this is "newer" than the existing // ones so replace the oldest one if ( replacements.size() == max_rep_per_node ){ DHTRouterContactImpl removed = (DHTRouterContactImpl) replacements.remove(0); // MGP: kicking out the oldest replacement router.notifyRemoved(removed); } }else{ // replace old unknown ones with newer unknown ones for (int i=0;i<replacements.size();i++){ DHTRouterContactImpl r = (DHTRouterContactImpl)replacements.get(i); if ( !r.hasBeenAlive()){ // MGP: kicking out an older replacement that was never alive router.notifyRemoved(r); replacements.remove(i); break; } } } }else{ try_ping = true; } } if ( replacements.size() == max_rep_per_node ){ // no room, drop the contact return( null ); } // MGP: notify observers contact added as a replacement replacement.setReplacement(); router.notifyAdded(replacement); replacements.add( replacement ); if ( try_ping ){ for (int i=0;i<buckets.size();i++){ DHTRouterContactImpl c = (DHTRouterContactImpl)buckets.get(i); // don't ping ourselves or someone already being pinged if ( !( router.isID(c.getID()) || c.getPingOutstanding())){ c.setPingOutstanding( true ); router.requestPing( c ); break; } } } return( replacement ); } protected DHTRouterContactImpl updateExistingNode( byte[] node_id, DHTRouterContactAttachment attachment, boolean known_to_be_alive ) { for (int k=0;k<buckets.size();k++){ DHTRouterContactImpl contact = (DHTRouterContactImpl)buckets.get(k); if ( Arrays.equals(node_id, contact.getID())){ if ( known_to_be_alive ){ // MGP: will update observers of updated status in this method alive( contact ); } // might be the same node but back after a restart. we need to // treat this differently as we need to kick off the "store" // events as required. int new_id = attachment.getInstanceID(); // if the new-id is zero this represents us hearing about a contact // indirectly (imported or returned as a query). In this case we // don't use this information as an indication of the target's // instance identity because it isn't! if ( new_id != 0 ){ int old_id = contact.getAttachment().getInstanceID(); if ( old_id != new_id ){ DHTLog.log( "Instance ID changed for " + DHTLog.getString( contact.getID())+ ": old = " + old_id + ", new = " + new_id ); contact.setAttachment( attachment ); // if the instance id was 0, this means that it was unknown // (e.g. contact imported). We still need to go ahead and treat // as a new node requestNodeAdd( contact, old_id != 0 ); } } return( contact ); } } // check replacements as well if ( replacements != null ){ for (int k=0;k<replacements.size();k++){ DHTRouterContactImpl contact = (DHTRouterContactImpl)replacements.get(k); if ( Arrays.equals(node_id, contact.getID())){ if ( known_to_be_alive ){ // MGP: will update observers of updated status in this method alive( contact ); } return( contact ); } } } return( null ); } protected void alive( DHTRouterContactImpl contact ) { // DHTLog.log( DHTLog.getString( contact.getID()) + ": alive" ); contact.setPingOutstanding( false ); // record whether was alive boolean was_alive = contact.isAlive(); if ( buckets.remove( contact )){ contact.setAlive(); if (!was_alive) { // MGP: notify observers that now alive router.notifyNowAlive(contact); } // MGP: simply reinserting, so do not notify observers that added to bucket buckets.add( contact ); }else if ( replacements.remove( contact )){ long last_time = contact.getFirstFailOrLastAliveTime(); contact.setAlive(); if (!was_alive) { // MGP: notify observers that now alive router.notifyNowAlive(contact); } // this is a good time to probe the contacts as we know a // replacement is alive and therefore in a position to replace a // dead bucket entry. Only do this if we haven't heard from this contact // recently if ( contact.getLastAliveTime() - last_time > 30000 ){ for (int i=0;i<buckets.size();i++){ DHTRouterContactImpl c = (DHTRouterContactImpl)buckets.get(i); // don't ping ourselves or someone already being pinged if ( !( router.isID(c.getID()) || c.getPingOutstanding())){ c.setPingOutstanding( true ); router.requestPing( c ); break; } } } // MGP: simply reinserting, so do not notify observers that added to replacments replacements.add( contact ); } } protected void dead( DHTRouterContactImpl contact, boolean force ) { // DHTLog.log( DHTLog.getString( contact.getID()) + ": dead" ); contact.setPingOutstanding( false ); // record whether was failing boolean was_failing = contact.isFailing(); if ( contact.setFailed() || force ){ // check the contact is still present if ( buckets.remove( contact )){ if (!was_failing) { // MGP: first notify observers that now failing router.notifyNowFailing(contact); } // MGP: notify that removed from bucket router.notifyRemoved(contact); if ( replacements != null && replacements.size() > 0 ){ // take most recent alive one and add to buckets boolean replaced = false; for (int i=replacements.size()-1;i>=0;i--){ DHTRouterContactImpl rep = (DHTRouterContactImpl)replacements.get(i); if ( rep.hasBeenAlive()){ DHTLog.log( DHTLog.getString( contact.getID()) + ": using live replacement " + DHTLog.getString(rep.getID())); // MGP: notify that a replacement was promoted to the bucket rep.setBucketEntry(); router.notifyLocationChanged(rep); replacements.remove( rep ); buckets.add( rep ); replaced = true; requestNodeAdd( rep, false ); break; } } // non alive - just take most recently added if ( !replaced ){ DHTRouterContactImpl rep = (DHTRouterContactImpl)replacements.remove( replacements.size() - 1 ); DHTLog.log( DHTLog.getString( contact.getID()) + ": using unknown replacement " + DHTLog.getString(rep.getID())); // MGP: notify that a replacement was promoted to the bucket rep.setBucketEntry(); router.notifyLocationChanged(rep); buckets.add( rep ); // add-node logic will ping the node if its not known to // be alive requestNodeAdd( rep, false ); } } }else{ if (!was_failing) { // MGP: first notify observers that now failing router.notifyNowFailing(contact); } // MGP: notify that removed from replacement list router.notifyRemoved(contact); replacements.remove( contact ); } } } protected void requestNodeAdd( DHTRouterContactImpl contact, boolean definite_change ) { // DOS problem here - if a node deliberately flicked between // instance IDs we'll get into an update frenzy. long now = SystemTime.getCurrentTime(); if ( now - contact.getLastAddedTime() > 10000 ){ contact.setLastAddedTime( now ); router.requestNodeAdd( contact ); }else{ // only produce a warning if this is a definite change from one id to // another (as opposed to a change from "unknown" to another) if ( definite_change ){ router.log( "requestNodeAdd for " + contact.getString() + " denied as too soon after previous "); } } } protected long getTimeSinceLastLookup() { long now = SystemTime.getCurrentTime(); if ( now < last_lookup_time ){ // clock changed, don't know so make as large as possible return( Long.MAX_VALUE ); } return( now - last_lookup_time ); } protected void setLastLookupTime() { last_lookup_time = SystemTime.getCurrentTime(); } public void print( String indent, String prefix ) { if ( left == null ){ router.log( indent + prefix + ": buckets = " + buckets.size() + contactsToString( buckets) + ", replacements = " + (replacements==null?"null":( replacements.size() + contactsToString( replacements ))) + (contains_router_node_id?" *":" ") + (this==router.getSmallestSubtree()?"SST":"") + " tsll=" + getTimeSinceLastLookup()); }else{ router.log( indent + prefix + ":" + (contains_router_node_id?" *":" ") + (this==router.getSmallestSubtree()?"SST":"")); left.print( indent + " ", prefix + "1" ); right.print( indent + " ", prefix + "0" ); } } protected String contactsToString( List contacts ) { String res = "{"; for (int i=0;i<contacts.size();i++){ res += (i==0?"":", ") + ((DHTRouterContactImpl)contacts.get(i)).getString(); } return( res + "}" ); } }