package net.tomp2p.relay;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.connection.Responder;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Buffer;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.message.NeighborSet;
import net.tomp2p.p2p.Peer;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMap;
import net.tomp2p.peers.PeerStatistic;
import net.tomp2p.rpc.DispatchHandler;
import net.tomp2p.rpc.NeighborRPC;
import net.tomp2p.rpc.RPC;
public class Forwarder extends DispatchHandler {
private final static Logger LOG = LoggerFactory.getLogger(Forwarder.class);
private final static AtomicLong messageCounter = new AtomicLong();
private final PeerConnection unreachablePeerConnection;
private List<Map<Number160, PeerStatistic>> peerMap;
private final boolean isSlow;
private final List<Message> buffer = Collections.synchronizedList(new ArrayList<Message>());
final private int bufferSize;
final private int bufferTimeoutSeconds;
private long lastAccess = System.currentTimeMillis();
public Forwarder(Peer peer, PeerConnection unreachablePeerConnection, boolean isSlow, int bufferTimeoutSeconds, int bufferSize) {
super(peer.peerBean(), peer.connectionBean());
this.unreachablePeerConnection = unreachablePeerConnection;
this.isSlow = isSlow;
this.bufferTimeoutSeconds = bufferTimeoutSeconds;
this.bufferSize = bufferSize;
}
private FutureDone<Message> forwardOrBuffer(final Message requestMessage) {
if(isSlow) {
final FutureDone<Message> futureDone = new FutureDone<Message>();
Message fastReply = createResponseMessage(requestMessage, Type.PARTIALLY_OK);
addToBuffer(requestMessage);
return futureDone.done(fastReply);
} else {
return forwardToUnreachable(requestMessage);
}
}
public FutureDone<Message> forwardToUnreachable(final Message message) {
// Send message via direct message through the open connection to the unreachable peer
LOG.debug("Sending {} to unreachable peer {}", message, unreachablePeerConnection.remotePeer());
final Message envelope = createMessage(unreachablePeerConnection.remotePeer(), RPC.Commands.RELAY.getNr(), Type.REQUEST_2);
try {
message.restoreContentReferences();
// add the message into the payload
envelope.buffer(RelayUtils.encodeMessage(message, connectionBean().channelServer().channelServerConfiguration()
.signatureFactory()));
} catch (Exception e) {
LOG.error("Cannot encode the message", e);
return new FutureDone<Message>().failed(e);
}
// always keep the connection open
envelope.keepAlive(true);
// this will be read RelayRPC.handlePiggyBackMessage
if(envelope.sender().ipv4Flag()) {
envelope.peerSocket4Address(envelope.sender().ipv4Socket());
}
if(envelope.sender().ipv6Flag()) {
envelope.peerSocket6Address(envelope.sender().ipv6Socket());
}
// holds the message that will be returned to he requester
final FutureDone<Message> futureDone = new FutureDone<Message>();
// Forward a message through the open peer connection to the unreachable peer.
FutureResponse fr = RelayUtils.send(unreachablePeerConnection, peerBean(), connectionBean(), envelope);
fr.addListener(new BaseFutureAdapter<FutureResponse>() {
public void operationComplete(FutureResponse future) throws Exception {
if (future.isSuccess()) {
InetSocketAddress senderSocket = message.recipientSocket();
if (senderSocket == null) {
senderSocket = unreachablePeerConnection.remotePeer().ipv4Socket().createTCPSocket();
}
InetSocketAddress recipientSocket = message.senderSocket();
if (recipientSocket == null) {
recipientSocket = message.sender().ipv4Socket().createTCPSocket();
}
Buffer buffer = future.responseMessage().buffer(0);
Message responseFromUnreachablePeer = RelayUtils.decodeMessage(buffer.buffer(), recipientSocket,
senderSocket, connectionBean().channelServer().channelServerConfiguration().signatureFactory());
responseFromUnreachablePeer.restoreContentReferences();
futureDone.done(responseFromUnreachablePeer);
} else {
futureDone.failed("Could not forward message over TCP channel");
}
}
});
return futureDone;
}
@Override
public void handleResponse(Message message, PeerConnection peerConnection,
boolean sign, final Responder responder) throws Exception {
// special treatment for ping and neighbor
if (message.command() == RPC.Commands.PING.getNr()) {
LOG.debug("Received message {} to handle ping for unreachable peer {}", message, unreachablePeerConnection.remotePeer());
handlePing(message, responder);
} else if (message.command() == RPC.Commands.NEIGHBOR.getNr()) {
LOG.debug("Received message {} to handle neighbor request for unreachable peer {}", message, unreachablePeerConnection.remotePeer());
handleNeigbhor(message, responder);
} else {
messageCounter.incrementAndGet();
LOG.debug("Received message {} to forward to unreachable peer 1 {}", message, unreachablePeerConnection.remotePeer());
FutureDone<Message> response = forwardOrBuffer(message);
response.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
Message answerMessage = future.object();
LOG.debug("Returning from relay to requester: 1 {}", answerMessage);
responder.response(answerMessage);
} else {
responder.failed(Type.DENIED, "Relaying message failed: 1 " + future.failedReason());
}
}
});
}
}
public void handleForward(final Message forwardMessage, final Message message, final Responder responder) {
messageCounter.incrementAndGet();
LOG.debug("Received message {} to forward to unreachable peer {}, orig: {}", forwardMessage, unreachablePeerConnection.remotePeer(), message);
final FutureDone<Message> response = forwardOrBuffer(forwardMessage);
response.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
final Message answerMessage = createResponseMessage(message, Type.OK);
LOG.debug("Returning from relay to requester: 2 {}", answerMessage);
responder.response(answerMessage);
} else {
responder.failed(Type.DENIED, "Relaying message failed: 2 " + future.failedReason());
}
}
});
}
/**
* When a ping message is received
*
* @param message
* @param responder
*/
private void handlePing(Message message, Responder responder) {
Message response = createResponseMessage(message, unreachablePeerConnection.isOpen() ? Type.OK : Type.DENIED, unreachablePeerConnection.remotePeer());
responder.response(response);
}
/**
* When a neighbor message is received
*
* @param message
* @param responder
*/
private void handleNeigbhor(final Message message, Responder responder) {
if (message.keyList().size() < 2) {
throw new IllegalArgumentException("We need the location and domain key at least");
}
if (!(message.type() == Type.REQUEST_1 || message.type() == Type.REQUEST_2 || message.type() == Type.REQUEST_3 || message
.type() == Type.REQUEST_4) && (message.command() == RPC.Commands.NEIGHBOR.getNr())) {
throw new IllegalArgumentException("Message content is wrong");
}
Number160 locationKey = message.key(0);
Collection<PeerAddress> neighbors = getNeighbors(locationKey, NeighborRPC.NEIGHBOR_SIZE);
if (neighbors == null) {
// return empty neighbor set
Message response = createResponseMessage(message, Type.NOT_FOUND, unreachablePeerConnection.remotePeer());
response.neighborsSet(new NeighborSet(-1, Collections.<PeerAddress> emptyList()));
responder.response(response);
return;
}
// Create response message and set neighbors
final Message responseMessage = createResponseMessage(message, Type.OK, unreachablePeerConnection.remotePeer());
// TODO: the relayed peer must be up-to-date here
// neighbors.add(peerConnection.remotePeer());
LOG.debug("found the following neighbors {}", neighbors);
NeighborSet neighborSet = new NeighborSet(NeighborRPC.NEIGHBOR_LIMIT, neighbors);
responseMessage.neighborsSet(neighborSet);
// we can't do fast get here, as we only send over the neighbors and not the keys stored
responder.response(responseMessage);
}
private SortedSet<PeerAddress> getNeighbors(Number160 id, int atLeast) {
LOG.trace("Answering routing request on behalf of unreachable peer {}, neighbors of {}", unreachablePeerConnection.remotePeer(),
id);
if (peerMap == null) {
return null;
} else {
SortedSet<PeerStatistic> closePeers = PeerMap.closePeers(unreachablePeerConnection.remotePeer().peerId(), id, NeighborRPC.NEIGHBOR_SIZE,
peerMap, null);
SortedSet<PeerAddress> result = new TreeSet<PeerAddress>(PeerMap.createXORAddressComparator(id));
for (PeerStatistic p : closePeers) {
result.add(p.peerAddress());
}
return result;
}
}
/**
* Returns the current peer map from the mobile device
*/
public final Collection<PeerAddress> getPeerMap() {
Collection<PeerAddress> peerAddresses = new ArrayList<PeerAddress>();
if (peerMap == null || peerMap.isEmpty()) {
return peerAddresses;
}
Collection<PeerStatistic> statistics = new ArrayList<PeerStatistic>();
for (Map<Number160, PeerStatistic> map : peerMap) {
statistics.addAll(map.values());
}
for (PeerStatistic peerStatistic : statistics) {
peerAddresses.add(peerStatistic.peerAddress());
}
return peerAddresses;
}
/**
* Update the peerMap of the unreachable peer
*
* @param peerMap the extracted peer map
* @param requestMessage the original message that contained the extracted peer map
* @param preparedResponse the response that will be sent to the unreachable peer
*/
public final void setPeerMap(List<Map<Number160, PeerStatistic>> peerMap, Message requestMessage,
Message preparedResponse) {
this.peerMap = peerMap;
checkSend();
}
private void addToBuffer(Message requestMessage) {
LOG.debug("add msg on peer {}, {}", peerBean().serverPeerAddress(), requestMessage);
buffer.add(requestMessage);
checkSend();
}
private void checkSend() {
LOG.debug("check buffer on peer {}", peerBean().serverPeerAddress());
if(buffer.size() > 0 && (buffer.size() > bufferSize || lastAccess + (bufferTimeoutSeconds * 1000) < System.currentTimeMillis())) {
forwardMessages(buffer);
lastAccess = System.currentTimeMillis();
}
}
private void forwardMessages(List<Message> buffer2) {
LOG.debug("empty buffer on peer {}", peerBean().serverPeerAddress());
final Message envelope = createMessage(unreachablePeerConnection.remotePeer(), RPC.Commands.RELAY.getNr(), Type.REQUEST_4);
// always keep the connection open
envelope.keepAlive(true);
ByteBuf bb = RelayUtils.composeMessageBuffer(buffered(), connectionBean().resourceConfiguration().signatureFactory());
envelope.buffer(new Buffer(bb));
// this will be read RelayRPC.handlePiggyBackMessage
if(envelope.sender().ipv4Flag()) {
envelope.peerSocket4Address(envelope.sender().ipv4Socket());
}
if(envelope.sender().ipv6Flag()) {
envelope.peerSocket6Address(envelope.sender().ipv6Socket());
}
// Forward a message through the open peer connection to the unreachable peer.
RelayUtils.send(unreachablePeerConnection, peerBean(), connectionBean(), envelope);
}
public List<Message> buffered() {
List<Message> retVal;
synchronized (buffer) {
retVal = new ArrayList<Message>(buffer);
}
buffer.clear();
return retVal;
}
public PeerAddress unreachablePeerAddress() {
return unreachablePeerConnection.remotePeer();
}
}