package net.tomp2p.holep;
import java.util.ArrayList;
import net.tomp2p.connection.Dispatcher;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.connection.Responder;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.holep.strategy.AbstractHolePStrategy;
import net.tomp2p.holep.strategy.HolePStrategy;
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.PeerAddress;
import net.tomp2p.relay.Forwarder;
import net.tomp2p.rpc.DispatchHandler;
import net.tomp2p.rpc.RPC;
import net.tomp2p.rpc.RPC.Commands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Jonas Wagner
*
* This class is responsible for the transmission of the holep setup and
* reply message for the hole punch procedure. The class will also start
* the hole punch procedure on the target peer side.
*
*/
public class HolePRPC extends DispatchHandler {
private static final Logger LOG = LoggerFactory.getLogger(HolePRPC.class);
private final Peer peer;
public HolePRPC(final Peer peer) {
super(peer.peerBean(), peer.connectionBean());
register(RPC.Commands.HOLEP.getNr());
this.peer = peer;
}
@Override
public void handleResponse(final Message message, final PeerConnection peerConnection, boolean sign, final Responder responder) throws Exception {
// This means, that a new Holepunch has been initiated.
if (message.type() == Message.Type.REQUEST_1) {
LOG.debug("New HolePunch process initiated from peer " + message.sender().peerId() + " to peer " + message.recipient().peerId()
+ " on ports: " + message.intList().toString());
forwardHolePunchMessage(message, responder);
}
// This means that the initiating peer has sent a holep setup message to
// this peer
else if (message.type() == Message.Type.REQUEST_2) {
LOG.debug("HolePunch initiated on peer: " + message.recipient().peerId());
handleHolePunch(message, responder);
} else {
throw new IllegalArgumentException("Message Content is wrong!");
}
}
/**
* This method is called by handleResponse(...) and initiates the hole
* punching procedure on the nat peer that needs to be contacted. It creates
* an {@link AbstractHolePStrategy} and waits then for the reply
* {@link Message} which the peer that needs to be contacted sends back to
* the initiating peer. The reply{@link Message} contains information about
* the holes which are punched currently.
*
* @param message
* @param responder
*/
@SuppressWarnings("static-access")
private void handleHolePunch(final Message message, final Responder responder) {
/*final NATType type = ((HolePInitiatorImpl) peer.peerBean().holePunchInitiator()).natType();
final HolePStrategy holePuncher = type.holePuncher(peer, message.intAt(0), peer.connectionBean().DEFAULT_UDP_IDLE_SECONDS, message);
final FutureDone<Message> replyMessage = holePuncher.replyHolePunch();
LOG.debug("Hole Punch attempt received. Start reply procedure.");
replyMessage.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
LOG.debug("Reply procedure successfully done. Now replying port information to HolePInitiator.");
responder.response(future.object());
} else {
handleFail(message, responder, "Fail while initiating the hole punching");
}
}
});*/
}
/**
* This method first forwards a initHolePunch request to start the hole
* punching procedure on the target peer. Then it waits for the response
* from the target peer and forwards this response back to the initiating
* peer.
*
* @param message
* @param responder
*/
private void forwardHolePunchMessage(final Message message, final Responder responder) {
final Forwarder forwarder = extractRelayForwarder(message);
if (forwarder != null) {
final Message forwardMessage = createForwardPortsMessage(message, forwarder.unreachablePeerAddress());
final FutureDone<Message> response = forwarder.forwardToUnreachable(forwardMessage);
response.addListener(new BaseFutureAdapter<FutureDone<Message>>() {
@Override
public void operationComplete(final FutureDone<Message> future) throws Exception {
if (future.isSuccess()) {
final Message answerMessage = createAnswerMessage(message, future.object());
LOG.debug("Returning from relay to requester: {}", answerMessage);
responder.response(answerMessage);
} else {
responder.failed(Type.EXCEPTION, "Relaying message failed: " + future.failedReason());
}
}
});
} else {
handleFail(message, responder, "No RelayForwarder registered for peerId=" + message.recipient().peerId().toString());
}
}
/**
* This method creates a the setUpMessage which needs to be forwarded to the
* unreachable peer.
*
* @param message
* @param recipient
* @return forwardMessage
*/
private Message createForwardPortsMessage(final Message message, final PeerAddress recipient) {
final Message forwardMessage = createMessage(recipient, RPC.Commands.HOLEP.getNr(), Message.Type.REQUEST_2);
forwardMessage.version(message.version());
forwardMessage.messageId(message.messageId());
// forward all ports to the unreachable peer2
forwardMessage.intValue(message.intAt(0));
duplicateBuffer(message, forwardMessage);
// transmit PeerAddress of unreachable Peer1
final NeighborSet ns = new NeighborSet(1, new ArrayList<PeerAddress>(1));
ns.add(message.sender());
forwardMessage.neighborsSet(ns);
return forwardMessage;
}
/**
* This method creates the message which is sent back from the relay to the
* initiating peer.
*
* @param replyMessage
* @param future
* @return
*/
private Message createAnswerMessage(final Message originalMessage, final Message replyMessage) {
final Message answerMessage = createResponseMessage(originalMessage, Message.Type.OK);
answerMessage.command(Commands.HOLEP.getNr());
// forward port information of unreachable peer2
answerMessage.intValue(replyMessage.intAt(0));
duplicateBuffer(replyMessage, answerMessage);
return answerMessage;
}
/**
* This method extracts a registered {@link BaseRelayForwarderRPC} from the
* {@link Dispatcher}. This RelayForwarder can then be used to extract the
* {@link PeerConnection} to the unreachable Peer we want to contact.
*
* @param unreachablePeerId
* the unreachable peer
* @return forwarder
*/
private Forwarder extractRelayForwarder(final Message message) {
final Dispatcher dispatcher = peer.connectionBean().dispatcher();
return (Forwarder) dispatcher.searchHandler(Forwarder.class, peer.peerID(), message.recipient().peerId());
}
/**
* This method simply duplicates the Buffer of a message.
*
* @param originalMessage
* @param copyMessage
*/
private void duplicateBuffer(final Message originalMessage, final Message copyMessage) {
for (Buffer buf : originalMessage.bufferList()) {
copyMessage.buffer(new Buffer(buf.buffer().duplicate()));
}
}
/**
* This method is called if something went wrong while the hole punching
* procedure. It responds then with a {@link Type}.EXCEPTION message.
*
* @param message
* @param responder
* @param failReason
*/
private void handleFail(final Message message, final Responder responder, final String failReason) {
LOG.error(failReason);
responder.response(createResponseMessage(message, Type.EXCEPTION));
}
}