/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.net.swarm;
import org.ethereum.net.rlpx.Node;
import org.ethereum.net.rlpx.discover.table.NodeEntry;
import org.ethereum.net.rlpx.discover.table.NodeTable;
import org.ethereum.net.swarm.bzz.BzzPeersMessage;
import org.ethereum.net.swarm.bzz.BzzProtocol;
import org.ethereum.net.swarm.bzz.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.util.*;
/**
* Serves as an interface to the Kademlia. Manages the database of Nodes reported
* by all the peers and selects from DB the nearest nodes to the specified hash Key
*
* Created by Anton Nashatyrev on 18.06.2015.
*/
public class Hive {
private final static Logger LOG = LoggerFactory.getLogger("net.bzz");
private PeerAddress thisAddress;
protected NodeTable nodeTable;
private Map<Node, BzzProtocol> connectedPeers = new IdentityHashMap<>();
public Hive(PeerAddress thisAddress) {
this.thisAddress = thisAddress;
nodeTable = new NodeTable(thisAddress.toNode());
}
public void start() {}
public void stop() {}
public PeerAddress getSelfAddress() {
return thisAddress;
}
public void addPeer(BzzProtocol peer) {
Node node = peer.getNode().toNode();
nodeTable.addNode(node);
connectedPeers.put(node, peer);
LOG.info("Hive added a new peer: " + peer);
peersAdded();
}
public void removePeer(BzzProtocol peer) {
nodeTable.dropNode(peer.getNode().toNode());
}
/**
* Finds the nodes which are not connected yet
* TODO review this method later
*/
public Collection<PeerAddress> getNodes(Key key, int max) {
List<Node> closestNodes = nodeTable.getClosestNodes(key.getBytes());
ArrayList<PeerAddress> ret = new ArrayList<>();
for (Node node : closestNodes) {
ret.add(new PeerAddress(node));
if (--max == 0) break;
}
return ret;
}
/**
* Returns the peers in the DB which are closest to the specified key
* but not more peers than {#maxCount}
*/
public Collection<BzzProtocol> getPeers(Key key, int maxCount) {
List<Node> closestNodes = nodeTable.getClosestNodes(key.getBytes());
ArrayList<BzzProtocol> ret = new ArrayList<>();
for (Node node : closestNodes) {
// TODO connect to Node
// ret.add(thisPeer.getPeer(new PeerAddress(node)));
BzzProtocol peer = connectedPeers.get(node);
if (peer != null) {
ret.add(peer);
if (--maxCount == 0) break;
} else {
LOG.info("Hive connects to node " + node);
NetStore.getInstance().worldManager.getActivePeer().connect(node.getHost(), node.getPort(), Hex.toHexString(node.getId()));
}
}
return ret;
}
public void newNodeRecord(PeerAddress addr) {}
/**
* Adds the nodes received in the {@link BzzPeersMessage}
*/
public void addPeerRecords(BzzPeersMessage req) {
for (PeerAddress peerAddress : req.getPeers()) {
nodeTable.addNode(peerAddress.toNode());
}
LOG.debug("Hive added new nodes: " + req.getPeers());
peersAdded();
}
protected void peersAdded() {
for (HiveTask task : new ArrayList<>(hiveTasks.keySet())) {
if (!task.peersAdded()) {
hiveTasks.remove(task);
LOG.debug("HiveTask removed from queue: " + task);
}
}
}
/**
* For testing
*/
public Map<Node, BzzProtocol> getAllEntries() {
Map<Node, BzzProtocol> ret = new LinkedHashMap<>();
for (NodeEntry entry : nodeTable.getAllNodes()) {
Node node = entry.getNode();
BzzProtocol bzz = connectedPeers.get(node);
ret.put(node, bzz);
}
return ret;
}
/**
* Adds a task with a search Key parameter. The task has a limited life time
* ({@link org.ethereum.net.swarm.Hive.HiveTask#expireTime} and a limited number of
* peers to process ({@link org.ethereum.net.swarm.Hive.HiveTask#maxPeers}).
* Until the task is alive and new Peer(s) is discovered by the Hive this task
* is invoked with another one closest Peer.
* This task may complete synchronously (i.e. before the method return) if the
* number of Peers in the Hive >= maxPeers for that task.
*/
public void addTask(HiveTask t) {
if (t.peersAdded()) {
LOG.debug("Added a HiveTask to queue: " + t);
hiveTasks.put(t, null);
}
}
private Map<HiveTask, Object> hiveTasks = new IdentityHashMap<>();
/**
* The task to be executed when another one closest Peer is discovered
* until the timeout or maxPeers is reached.
*/
public abstract class HiveTask {
Key targetKey;
Map<BzzProtocol, Object> processedPeers = new IdentityHashMap<>();
long expireTime;
int maxPeers;
public HiveTask(Key targetKey, long timeout, int maxPeers) {
this.targetKey = targetKey;
this.expireTime = Util.curTime() + timeout;
this.maxPeers = maxPeers;
}
/**
* Notifies the task that new peers were connected.
* @return true if the task wants to wait further for another peers
* false if the task is completed
*/
public boolean peersAdded() {
if (Util.curTime() > expireTime) return false;
Collection<BzzProtocol> peers = getPeers(targetKey, maxPeers);
for (BzzProtocol peer : peers) {
if (!processedPeers.containsKey(peer)) {
processPeer(peer);
processedPeers.put(peer, null);
if (processedPeers.size() > maxPeers) {
return false;
}
}
}
return true;
}
protected abstract void processPeer(BzzProtocol peer);
}
}