package lbms.plugins.mldht.kad; import java.net.InetAddress; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; 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 lbms.plugins.mldht.kad.DHT.DHTtype; /** * @author Damokles * */ public class Database { private Map<Key, Set<DBItem>> items; private Map<ByteWrapper, Long> tokens; private DatabaseStats stats; Database() { stats = new DatabaseStats(); items = new HashMap<Key, Set<DBItem>>(); tokens = new HashMap<ByteWrapper, Long>(); } /** * Store an entry in the database * * @param key * The key * @param dbi * The DBItem to store */ void store(Key key, DBItem dbi) { synchronized (items) { if (!items.containsKey(key)) { insert(key); } Set<DBItem> keyEntries = items.get(key); if (!keyEntries.remove(dbi)) stats.setItemCount(stats.getItemCount() + 1); keyEntries.add(dbi); } } /** * Get max_entries items from the database, which have the same key, items * are taken randomly from the list. If the key is not present no items will * be returned, if there are fewer then max_entries items for the key, all * entries will be returned * * @param key * The key to search for * @param dbl * The list to store the items in * @param max_entries * The maximum number entries */ void sample(Key key, List<DBItem> tdbl, int max_entries, DHTtype forType) { synchronized (items) { if (items.containsKey(key)) { List<DBItem> dbl = new ArrayList<DBItem>(items.get(key)); Collections.shuffle(dbl); int num_added = 0; for (DBItem item : dbl) { if(forType.ADDRESS_ENTRY_LENGTH != item.getData().length) continue; if (num_added >= max_entries) { break; } tdbl.add(item); num_added++; } } } } /** * Expire all items older then 30 minutes * * @param now * The time it is now (we pass this along so we only have to * calculate it once) */ void expire(long now) { int removed = 0; List<Key> keysToRemove = new ArrayList<Key>(); synchronized (items) { int itemCount = 0; for (Key k : items.keySet()) { Set<DBItem> dbl = items.get(k); List<DBItem> tmp = new ArrayList<DBItem>(dbl); Collections.sort(tmp, DBItem.ageOrdering); while (dbl.size() > 0 && tmp.get(0).expired(now)) { dbl.remove(tmp.remove(0)); removed++; } if (dbl.size() == 0) { keysToRemove.add(k); } else itemCount += dbl.size(); } items.keySet().removeAll(keysToRemove); stats.setKeyCount(items.size()); stats.setItemCount(itemCount); } List<ByteWrapper> tokensToRemove = new ArrayList<ByteWrapper>(); synchronized (tokens) { for (ByteWrapper t : tokens.keySet()) { if (now - tokens.get(t) > 3 * 60 * 1000) { //tokens are invalid after 3 minutes tokensToRemove.add(t); } } tokens.keySet().removeAll(tokensToRemove); } } /** * Generate a write token, which will give peers write access to the DB. * * @param ip * The IP of the peer * @param port * The port of the peer * @return A Key */ ByteWrapper genToken(InetAddress ip, int port, Key lookupKey) { byte[] tdata = new byte[30 + ip.getAddress().length]; long now = System.currentTimeMillis(); // generate a hash of the ip port and the current time // should prevent anybody from crapping things up ByteBuffer bb = ByteBuffer.wrap(tdata); bb.put(ip.getAddress()); bb.putShort((short) port); bb.putLong(now); bb.put(lookupKey.getHash()); try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(tdata); ByteWrapper token = new ByteWrapper(md.digest()); // keep track of the token, tokens will expire after a while synchronized (tokens) { tokens.put(token, now); } return token; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return new ByteWrapper(new byte[20]); } } /** * Check if a received token is OK. * * @param token * The token received * @param ip * The ip of the sender * @param port * The port of the sender * @return true if the token was given to this peer, false other wise */ boolean checkToken(ByteWrapper token, InetAddress ip, int port, Key lookupKey) { synchronized (tokens) { // the token must be in the map if (!tokens.containsKey(token)) { DHT.logDebug("Received Unknown token from " + ip.getHostAddress()); return false; } // in the map so now get the timestamp and regenerate the token // using the IP and port of the sender long ts = tokens.get(token); byte[] tdata = new byte[30 + ip.getAddress().length]; ByteBuffer bb = ByteBuffer.wrap(tdata); bb.put(ip.getAddress()); bb.putShort((short) port); bb.putLong(ts); bb.put(lookupKey.getHash()); try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(tdata); ByteWrapper ct = new ByteWrapper(md.digest()); // compare the generated token to the one received if (!token.equals(ct)) // not good, this peer didn't went through the proper channels { DHT.logDebug("Received Invalid token from " + ip.getHostAddress()); return false; } // expire the token tokens.remove(token); return true; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return false; } } } /// Test wether or not the DB contains a key boolean contains(Key key) { synchronized (items) { return items.containsKey(key); } } /// Insert an empty item (only if it isn't already in the DB) void insert(Key key) { synchronized (items) { Set<DBItem> dbl = items.get(key); if (dbl == null) { items.put(key, new HashSet<DBItem>()); stats.setKeyCount(items.size()); } } } /** * @return the stats */ public DatabaseStats getStats() { return stats; } }