/* * Mojito Distributed Hash Table (Mojito DHT) * Copyright (C) 2006-2007 LimeWire LLC * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.limewire.mojito.db.impl; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.collection.IntHashMap; import org.limewire.io.NetworkUtils; import org.limewire.mojito.KUID; import org.limewire.mojito.db.DHTValueEntity; import org.limewire.mojito.db.Database; import org.limewire.mojito.db.DatabaseSecurityConstraint; import org.limewire.mojito.routing.Contact; import org.limewire.mojito.settings.DatabaseSettings; import org.limewire.mojito.util.ContactUtils; /* * Multiple values per key and one value per nodeId under a certain key * * valueId * nodeId * value * nodeId * value * valueId * nodeId * value * nodeId * value * nodeId * value */ /** * Adds, removes and stores a {@link DHTValueEntity} to a * database. Values are stored in-memory. */ /* TODO: For more advanced features we need some definition for * DHTValues (non-signed values cannot replace signed values and * what not). */ public class DatabaseImpl implements Database { private static final long serialVersionUID = -4857315774747734947L; private static final Log LOG = LogFactory.getLog(DatabaseImpl.class); public static final int IPV4_ADDRESS_NETMASK = 0xFFFFFFFF; /** LOCKING: this */ private final Map<KUID, DHTValueEntityBag> database = new HashMap<KUID, DHTValueEntityBag>(); /** * The DatabaseSecurityConstraint handle. */ private volatile DatabaseSecurityConstraint securityConstraint = new DefaultDatabaseSecurityConstraint(); /** * A Map of masked IP address to number of values. */ private final IntHashMap<AtomicInteger> valuesPerNetwork = new IntHashMap<AtomicInteger>(); /** * A Map of IP address to number of values. */ private final IntHashMap<AtomicInteger> valuesPerAddress = new IntHashMap<AtomicInteger>(); /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#setDatabaseSecurityConstraint(com.limegroup.mojito.db.DatabaseSecurityConstraint) */ public void setDatabaseSecurityConstraint( DatabaseSecurityConstraint securityConstraint) { if (securityConstraint == null) { securityConstraint = new DefaultDatabaseSecurityConstraint(); } this.securityConstraint = securityConstraint; } /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#getKeyCount() */ public synchronized int getKeyCount() { return database.size(); } /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#getValueCount() */ public synchronized int getValueCount() { return values().size(); } /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#clear() */ public synchronized void clear() { database.clear(); } /* * (non-Javadoc) * @see org.limewire.mojito.db.Database#store(org.limewire.mojito.db.DHTValueEntity) */ public synchronized boolean store(DHTValueEntity entity) { if (!allowStore(entity)) { return false; } if (entity.getValue().size() == 0) { return remove(entity.getPrimaryKey(), entity.getSecondaryKey()) != null; } else { return add(entity); } } /** * Adds the given <code>DHTValue</code> to the Database succeeded. * @return true if adding the <code>DHTValueEntity</code> succeeded */ public synchronized boolean add(DHTValueEntity entity) { KUID primaryKey = entity.getPrimaryKey(); DHTValueEntityBag bag = database.get(primaryKey); if (bag == null) { bag = new DHTValueEntityBag(this, primaryKey); } if (bag.add(entity)) { if (!database.containsKey(primaryKey)) { database.put(primaryKey, bag); } incrementValuesPerAddress(entity); incrementValuesPerNetwork(entity); return true; } return false; } /* * (non-Javadoc) * @see org.limewire.mojito.db.Database#remove(org.limewire.mojito.KUID, org.limewire.mojito.KUID) */ public synchronized DHTValueEntity remove(KUID primaryKey, KUID secondaryKey) { DHTValueEntity entity = null; DHTValueEntityBag bag = database.get(primaryKey); if (bag != null && (entity = bag.remove(secondaryKey)) != null) { if (bag.isEmpty()) { database.remove(primaryKey); } decrementValuesPerAddress(entity); decrementValuesPerNetwork(entity); } return entity; } /** * Returns the number of values that are currently stored under * the same Class C Network. */ public synchronized int getValuesPerNetwork(DHTValueEntity entity) { return getValueCount(entity, valuesPerNetwork, NetworkUtils.CLASS_C_NETMASK); } /** * Returns the number of values that are currently stored under * the same IP Address. */ public synchronized int getValuesPerAddress(DHTValueEntity entity) { return getValueCount(entity, valuesPerAddress, IPV4_ADDRESS_NETMASK); } /** * A helper method to get the number of values that are currently stored * under a certain masked IP address. */ private static int getValueCount(DHTValueEntity entity, IntHashMap<AtomicInteger> map, int netmask) { if (entity.isLocalValue()) { return 0; } Contact node = entity.getCreator(); InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); if (addr instanceof Inet4Address) { int masked = NetworkUtils.getMaskedIP(addr, netmask); AtomicInteger count = map.get(masked); if (count != null) { return count.get(); } } return 0; } /** * Increments and returns the number of values that are stored under the * same Class C Network. */ private int incrementValuesPerNetwork(DHTValueEntity entity) { return incrementValueCount(entity, valuesPerNetwork, NetworkUtils.CLASS_C_NETMASK); } /** * Increments and returns the number of values that are stored under the * same IP address. */ private int incrementValuesPerAddress(DHTValueEntity entity) { return incrementValueCount(entity, valuesPerAddress, IPV4_ADDRESS_NETMASK); } /** * A helper method to increment the number of values that are stored * under a certain masked IP address. */ private static int incrementValueCount(DHTValueEntity entity, IntHashMap<AtomicInteger> map, int netmask) { if (entity.isLocalValue()) { return 0; } Contact node = entity.getCreator(); InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); if (addr instanceof Inet4Address) { int masked = NetworkUtils.getMaskedIP(addr, netmask); AtomicInteger count = map.get(masked); if (count == null) { count = new AtomicInteger(0); map.put(masked, count); } return count.incrementAndGet(); } return 0; } /** * Decrements and returns the number of values that are currently * stored under the same Class C Network. */ private int decrementValuesPerNetwork(DHTValueEntity entity) { return decrementValueCount(entity, valuesPerNetwork, NetworkUtils.CLASS_C_NETMASK); } /** * Decrements and returns the number of values that are currently * stored under the same IP address. */ private int decrementValuesPerAddress(DHTValueEntity entity) { return decrementValueCount(entity, valuesPerAddress, IPV4_ADDRESS_NETMASK); } /** * A helper method to decrement the number of values that are stored * under a certain masked IP address. */ private static int decrementValueCount(DHTValueEntity entity, IntHashMap<AtomicInteger> map, int netmask) { if (entity.isLocalValue()) { return 0; } Contact node = entity.getCreator(); InetAddress addr = ((InetSocketAddress)node.getContactAddress()).getAddress(); if (addr instanceof Inet4Address) { int masked = NetworkUtils.getMaskedIP(addr, netmask); AtomicInteger count = map.get(masked); if (count != null) { int value = count.decrementAndGet(); if (value == 0) { map.remove(masked); } return value; } } return 0; } /* * (non-Javadoc) * @see org.limewire.mojito.db.Database#getRequestLoad(org.limewire.mojito.KUID, boolean) */ public synchronized float getRequestLoad(KUID primaryKey, boolean incrementLoad) { DHTValueEntityBag bag = database.get(primaryKey); if (bag != null) { return bag.getRequestLoad(incrementLoad); } return 0f; } /** * An internal helper method that checks for possible flooding * and then delegates calls to the <code>DatabaseSecurityConstraint</code> instance * if possible. */ private boolean allowStore(DHTValueEntity entity) { if (entity.isLocalValue()) { return true; } if (DatabaseSettings.VALIDATE_VALUE_CREATOR.getValue() && !entity.isDirect()) { if (!ContactUtils.isValidSocketAddress(entity.getCreator())) { if (LOG.isInfoEnabled()) { LOG.info("The Creator of " + entity + " has an invalid address"); } return false; } if (ContactUtils.isPrivateAddress(entity.getCreator())) { if (LOG.isInfoEnabled()) { LOG.info("The Creator of " + entity + " has a private address"); } return false; } } if (entity.getValue().size() != 0) { int valuesPerAddress = getValuesPerAddress(entity); if (DatabaseSettings.LIMIT_VALUES_PER_ADDRESS.getValue() && valuesPerAddress >= DatabaseSettings.MAX_VALUES_PER_ADDRESS.getValue()) { return false; } int valuesPerNetwork = getValuesPerNetwork(entity); if (DatabaseSettings.LIMIT_VALUES_PER_NETWORK.getValue() && valuesPerNetwork >= DatabaseSettings.MAX_VALUES_PER_NETWORK.getValue()) { return false; } } // Check with the security constraint now DHTValueEntityBag bag = database.get(entity.getPrimaryKey()); DatabaseSecurityConstraint dbsc = securityConstraint; if (dbsc != null && bag != null) { return dbsc.allowStore(this, bag.getValues(false), entity); } return true; } /** * For internal use only. */ public synchronized DHTValueEntityBag getBag(KUID valueId) { return database.get(valueId); } /* * (non-Javadoc) * @see org.limewire.mojito.db.Database#get(org.limewire.mojito.KUID) */ public synchronized Map<KUID, DHTValueEntity> get(KUID valueId) { DHTValueEntityBag bag = database.get(valueId); if (bag != null) { return bag.getValues(true); } return Collections.emptyMap(); } /* * (non-Javadoc) * @see org.limewire.mojito.db.Database#contains(org.limewire.mojito.KUID, org.limewire.mojito.KUID) */ public synchronized boolean contains(KUID primaryKey, KUID secondaryKey) { DHTValueEntityBag bag = database.get(primaryKey); return (bag != null && bag.contains(secondaryKey)); } /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#keySet() */ public synchronized Set<KUID> keySet() { return new HashSet<KUID>(database.keySet()); } /* * (non-Javadoc) * @see com.limegroup.mojito.db.Database#values() */ public synchronized Collection<DHTValueEntity> values() { List<DHTValueEntity> values = new ArrayList<DHTValueEntity>(getKeyCount() * 2); for (DHTValueEntityBag bag : database.values()) { values.addAll(bag.getValues(false).values()); } return values; } @Override public synchronized String toString() { StringBuilder buffer = new StringBuilder(); for (DHTValueEntityBag bag : database.values()) { buffer.append(bag.toString()); } buffer.append("-------------\n"); buffer.append("TOTAL: ").append(getKeyCount()) .append("/").append(getValueCount()).append("\n"); return buffer.toString(); } }