/*
* Copyright 2009 Thomas Bocek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.tomp2p.rpc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.connection2.ConnectionBean;
import net.tomp2p.connection2.ConnectionConfiguration;
import net.tomp2p.connection2.PeerBean;
import net.tomp2p.connection2.RequestHandler;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message2.Type;
import net.tomp2p.message.Message2;
import net.tomp2p.message.NeighborSet;
import net.tomp2p.p2p.PeerReachable;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.utils.Timings;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Ping message handler. Also used for NAT detection and other things.
*
* @author Thomas Bocek
*
*/
public class PingRPC extends DispatchHandler {
private static final Logger LOG = LoggerFactory.getLogger(PingRPC.class);
public static final byte PING_COMMAND = 0;
public static final int WAIT_TIME = 10 * 1000;
private final List<PeerReachable> listeners = new CopyOnWriteArrayList<PeerReachable>();
// used for testing and debugging
private final boolean enable;
private final boolean wait;
/**
* Creates an new handshake RPC with listeners.
*
* @param peerBean
* The peer bean
* @param connectionBean
* The connection bean
*/
public PingRPC(final PeerBean peerBean, final ConnectionBean connectionBean) {
this(peerBean, connectionBean, true, true, false);
}
/**
* Constructor that is only called from this class or from testcases.
*
* @param peerBean
* The peer bean
* @param connectionBean
* The connection bean
* @param listeners
* The listeners to notify when this peer address (IP) is changed
* @param enable
* Used for test cases, set to true in production
* @param register
* Used for test cases, set to true in production
* @param wait
* Used for test cases, set to false in production
*/
PingRPC(final PeerBean peerBean, final ConnectionBean connectionBean,
final boolean enable, final boolean register,
final boolean wait) {
super(peerBean, connectionBean);
this.enable = enable;
this.wait = wait;
if (register) {
connectionBean.dispatcher().registerIoHandler(peerBean.serverPeerAddress().getPeerId(), this,
PING_COMMAND);
}
}
/**
* Ping with UDP or TCP, but do not send yet.
*
* @param remotePeer
* The destination peer
* @return the request handler, where we can call sendUDP(), or sendTCP()
*/
public RequestHandler<FutureResponse> ping(final PeerAddress remotePeer,
final ConnectionConfiguration configuration) {
return createHandler(remotePeer, Type.REQUEST_1, configuration);
}
/**
* Ping a UDP peer.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingUDP(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
return ping(remotePeer, configuration).sendUDP(channelCreator);
}
/**
* Ping a UDP peer using layer 2 broadcast.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingBroadcastUDP(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
return ping(remotePeer, configuration).sendBroadcastUDP(channelCreator);
}
/**
* Ping a TCP peer.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingTCP(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
return ping(remotePeer, configuration).sendTCP(channelCreator);
}
/**
* Ping a UDP peer, but don't expect an answer.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse fireUDP(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
return createHandler(remotePeer, Type.REQUEST_FF_1, configuration).fireAndForgetUDP(channelCreator);
}
/**
* Ping a TCP peer, but don't expect an answer.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse fireTCP(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
return createHandler(remotePeer, Type.REQUEST_FF_1, configuration).fireAndForgetTCP(channelCreator);
}
/**
* Ping a UDP peer, and find out how the other peer sees us.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingUDPDiscover(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
final FutureResponse futureResponse = createDiscoverHandler(remotePeer);
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(), configuration)
.sendUDP(channelCreator);
}
/**
* Ping a TCP peer, and find out how the other peer sees us.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingTCPDiscover(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
final FutureResponse futureResponse = createDiscoverHandler(remotePeer);
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(), configuration)
.sendTCP(channelCreator);
}
/**
* Ping a UDP peer, and request the other peer to ping us on our public address with a fire and forget message.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingUDPProbe(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
final Message2 message = createMessage(remotePeer, PING_COMMAND, Type.REQUEST_3);
FutureResponse futureResponse = new FutureResponse(message);
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(), configuration)
.sendUDP(channelCreator);
}
/**
* Ping a TCP peer, and request the other peer to ping us on our public address with a fire and forget message.
*
* @param remotePeer
* The destination peer
* @param channelCreator
* The channel creator where we create a UPD channel
* @return The future that will be triggered when we receive an answer or something fails.
*/
public FutureResponse pingTCPProbe(final PeerAddress remotePeer, final ChannelCreator channelCreator,
final ConnectionConfiguration configuration) {
final Message2 message = createMessage(remotePeer, PING_COMMAND, Type.REQUEST_3);
FutureResponse futureResponse = new FutureResponse(message);
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(), configuration)
.sendTCP(channelCreator);
}
/**
* Create a request handler.
*
* @param remotePeer
* The destination peer
* @param type
* The type of the request
* @return The request handler
*/
private RequestHandler<FutureResponse> createHandler(final PeerAddress remotePeer, final Type type,
final ConnectionConfiguration configuration) {
final Message2 message = createMessage(remotePeer, PING_COMMAND, type);
final FutureResponse futureResponse = new FutureResponse(message);
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(), configuration);
}
/**
* Create a discover handler.
*
* @param remotePeer
* The destination peer
* @return The future of this discover handler
*/
private FutureResponse createDiscoverHandler(final PeerAddress remotePeer) {
final Message2 message = createMessage(remotePeer, PING_COMMAND, Type.REQUEST_2);
message.setNeighborsSet(createNeighborSet(peerBean().serverPeerAddress()));
return new FutureResponse(message);
}
/**
* Create a neighbor set with one peer. We only support sending a neighbor set, so we need this wrapper class.
*
* @param self
* The peer that should be stored in the neighborset
* @return The neighborset with exactly one peer
*/
private NeighborSet createNeighborSet(final PeerAddress self) {
Collection<PeerAddress> tmp = new ArrayList<PeerAddress>();
tmp.add(self);
return new NeighborSet(-1, tmp);
}
@Override
public Message2 handleResponse(final Message2 message, final boolean sign) throws Exception {
if (!((message.getType() == Type.REQUEST_FF_1 || message.getType() == Type.REQUEST_1
|| message.getType() == Type.REQUEST_2 || message.getType() == Type.REQUEST_3) && message
.getCommand() == PING_COMMAND)) {
throw new IllegalArgumentException("Message content is wrong");
}
final Message2 responseMessage;
// probe
if (message.getType() == Type.REQUEST_3) {
LOG.debug("reply to probing, fire message to {}", message.getSender());
responseMessage = createResponseMessage(message, Type.OK);
if (message.isUdp()) {
connectionBean().reservation().create(1, 0)
.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
FutureResponse futureResponse = fireUDP(message.getSender(), future
.getChannelCreator(), connectionBean().channelServer()
.channelServerConfiguration());
Utils.addReleaseListener(future.getChannelCreator(), futureResponse);
} else {
LOG.warn("handleResponse for REQUEST_3 failed (UDP) {}",
future.getFailedReason());
}
}
});
} else {
connectionBean().reservation().create(0, 1)
.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
FutureResponse futureResponse = fireTCP(message.getSender(), future
.getChannelCreator(), connectionBean().channelServer()
.channelServerConfiguration());
Utils.addReleaseListener(future.getChannelCreator(), futureResponse);
} else {
LOG.warn("handleResponse for REQUEST_3 failed (TCP) {}",
future.getFailedReason());
}
}
});
}
} else if (message.getType() == Type.REQUEST_2) { // discover
LOG.debug("reply to discover, found {}", message.getSender());
responseMessage = createResponseMessage(message, Type.OK);
responseMessage.setNeighborsSet(createNeighborSet(message.getSender()));
} else if (message.getType() == Type.REQUEST_1) { // regular ping
LOG.debug("reply to regular ping {}", message.getSender());
// test if this is a broadcast message to ourselves. If it is, do not
// reply.
if (message.getSender().getPeerId().equals(peerBean().serverPeerAddress().getPeerId())
&& message.getRecipient().getPeerId().equals(Number160.ZERO)) {
LOG.debug("don't reply, we are on the same peer");
return message;
}
if (enable) {
responseMessage = createResponseMessage(message, Type.OK);
if (wait) {
Timings.sleepUninterruptibly(WAIT_TIME);
}
} else {
responseMessage = null;
LOG.debug("do not reply");
// used for debugging
if (wait) {
Timings.sleepUninterruptibly(WAIT_TIME);
}
}
} else { // fire and forget - if (message.getType() == Type.REQUEST_FF_1)
// we received a fire and forget ping. This means we are reachable
// from outside
responseMessage = null;
PeerAddress serverAddress = peerBean().serverPeerAddress();
if (message.isUdp()) {
PeerAddress newServerAddress = serverAddress.changeFirewalledUDP(false);
peerBean().serverPeerAddress(newServerAddress);
synchronized (listeners) {
for (PeerReachable listener : listeners) {
listener.peerWellConnected(newServerAddress, message.getSender(), false);
}
}
} else {
PeerAddress newServerAddress = serverAddress.changeFirewalledTCP(false);
peerBean().serverPeerAddress(newServerAddress);
synchronized (listeners) {
for (PeerReachable listener : listeners) {
listener.peerWellConnected(newServerAddress, message.getSender(), true);
}
}
}
}
return responseMessage;
}
public void addPeerReachableListener(PeerReachable peerReachable) {
listeners.add(peerReachable);
}
public void removePeerReachableListener(PeerReachable peerReachable) {
listeners.remove(peerReachable);
}
}