Package net.tomp2p.relay

Source Code of net.tomp2p.relay.RelayRPC

package net.tomp2p.relay;

import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import net.tomp2p.connection.ConnectionConfiguration;
import net.tomp2p.connection.Dispatcher;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.connection.Responder;
import net.tomp2p.connection.SignatureFactory;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Buffer;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.p2p.Peer;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress;
import net.tomp2p.relay.android.AndroidForwarderRPC;
import net.tomp2p.relay.android.MessageBufferConfiguration;
import net.tomp2p.relay.tcp.OpenTCPForwarderRPC;
import net.tomp2p.rpc.DispatchHandler;
import net.tomp2p.rpc.RPC;
import net.tomp2p.rpc.RPC.Commands;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RelayRPC extends DispatchHandler {

    private static final Logger LOG = LoggerFactory.getLogger(RelayRPC.class);
    private final Peer peer;
    private final ConnectionConfiguration config;
 
    // used when it should serve as an Android relay server
    private final MessageBufferConfiguration bufferConfig;
 
  // holds the forwarder for each client
  private final Map<Number160, BaseRelayForwarderRPC> forwarders;

    /**
   * This variable is needed, because a relay overwrites every RPC of an
   * unreachable peer with another RPC called {@link RelayForwarderRPC}. This
   * variable is forwarded to the {@link RelayForwarderRPC} in order to
   * guarantee the existence of a {@link RconRPC}. Without this variable, no
   * reverse connections would be possible.
   *
   * @author jonaswagner
   */
  private final RconRPC rconRPC;

  /**
     * Register the RelayRPC. After the setup, the peer is ready to act as a
     * relay if asked by an unreachable peer.
     *
     * @param peer
     *            The peer to register the RelayRPC
     * @param rconRPC the reverse connection RPC
     * @param gcmAuthToken the authentication key for Google cloud messaging
     * @return
     */
  public RelayRPC(Peer peer, RconRPC rconRPC, MessageBufferConfiguration bufferConfig, ConnectionConfiguration config) {
        super(peer.peerBean(), peer.connectionBean());
        this.peer = peer;
    this.bufferConfig = bufferConfig;
    this.config = config;
        this.forwarders = new ConcurrentHashMap<Number160, BaseRelayForwarderRPC>();
        this.rconRPC = rconRPC;
       
        // register this handler
        register(RPC.Commands.RELAY.getNr());
    }

    /**
     * Receive a message at the relay server and the relay client
     */
    @Override
    public void handleResponse(final Message message, PeerConnection peerConnection, final boolean sign, Responder responder) throws Exception {
        LOG.debug("Received RPC message {}", message);
        if (message.type() == Type.REQUEST_1 && message.command() == RPC.Commands.RELAY.getNr()) {
          // The relay peer receives the setup message from the unreachable peer
          if(message.intList().isEmpty()) {
                throw new IllegalArgumentException("Setup message should contain an integer value specifying the type");
          }
         
          Integer deviceType = message.intAt(0);
          if(deviceType == RelayType.OPENTCP.ordinal()) {
            // request from unreachable peer to the relay
            handleSetupTCP(message, peerConnection, responder);
          } else if(deviceType == RelayType.ANDROID.ordinal()) {
            // request from mobile device to the relay
            handleSetupAndroid(message, peerConnection, responder);
          } else {
                throw new IllegalArgumentException("Unknown relay type: " + deviceType);
          }
        } else if (message.type() == Type.REQUEST_2 && message.command() == RPC.Commands.RELAY.getNr()) {
          // The unreachable peer receives wrapped messages from the relay
            handlePiggyBackedMessage(message, responder);
        } else if (message.type() == Type.REQUEST_3 && message.command() == RPC.Commands.RELAY.getNr()) {
          // the relay server receives the update of the routing table regularly from the unrachable peer
            handleMap(message, responder);
        } else if(message.type() == Type.REQUEST_4 && message.command() == RPC.Commands.RELAY.getNr()) {
          // An android unreachable peer requests the buffer
          handleBufferRequest(message, responder);
        } else if(message.type() == Type.REQUEST_5 && message.command() == RPC.Commands.RELAY.getNr()) {
          // A late response
          handleLateResponse(message, peerConnection, sign, responder);
        } else {
            throw new IllegalArgumentException("Message content is wrong");
        }
    }

  public Peer peer() {
        return this.peer;
    }
 
  /**
   * Convenience method
   * @return the signature factory
   */
  private SignatureFactory signatureFactory() {
    return connectionBean().channelServer().channelServerConfiguration().signatureFactory();
  }
 
  /**
   * Convenience method
   * @return the dispatcher of this peer
   */
  private Dispatcher dispatcher() {
    return peer().connectionBean().dispatcher();
  }
 
  /**
   * @return all unreachable peers currently connected to this relay node
   */
  public Set<PeerAddress> unreachablePeers() {
    Set<PeerAddress> unreachablePeers = new HashSet<PeerAddress>(forwarders.size());
    for (BaseRelayForwarderRPC forwarder : forwarders.values()) {
      unreachablePeers.add(forwarder.unreachablePeerAddress());
    }
    return unreachablePeers;
  }

    /**
     * Open a TCP connection to the unreachable peer
     */
    private void handleSetupTCP(Message message, final PeerConnection peerConnection, Responder responder) {
        if (peerBean().serverPeerAddress().isRelayed()) {
            // peer is behind a NAT as well -> deny request
          LOG.warn("I cannot be a relay since I'm relayed as well! {}", message);
            responder.response(createResponseMessage(message, Type.DENIED));
            return;
        }

        // register relay forwarder
        OpenTCPForwarderRPC tcpForwarder = new OpenTCPForwarderRPC(peerConnection, peer, config);
        registerRelayForwarder(tcpForwarder);

        LOG.debug("I'll be your relay! {}", message);
        responder.response(createResponseMessage(message, Type.OK));
    }
   
    /**
     * An android device is behind a firewall and wants to be relayed
     */
    private void handleSetupAndroid(Message message, final PeerConnection peerConnection, Responder responder) {
        if(message.bufferList().size() < 2) {
          LOG.error("Device {} did not send any GCM registration id or the authentication key", peerConnection.remotePeer());
            responder.response(createResponseMessage(message, Type.DENIED));
            return;
        }
       
    String registrationId = RelayUtils.decodeString(message.buffer(0));
    if(registrationId == null) {
      LOG.error("Cannot decode the registrationID from the message");
            responder.response(createResponseMessage(message, Type.DENIED));
            return;
    }
   
    String authenticationKey = RelayUtils.decodeString(message.buffer(1));
    if(authenticationKey == null) {
      LOG.error("Cannot decode the authentication key from the messsage");
      responder.response(createResponseMessage(message, Type.DENIED));
          return;
    }
   
    Integer mapUpdateInterval = message.intAt(1);
    if(mapUpdateInterval == null) {
      LOG.error("Android device did not send the peer map update interval.");
      responder.response(createResponseMessage(message, Type.DENIED));
          return;
    }
       
    LOG.debug("Hello Android device! You'll be relayed over GCM. {}", message);
    AndroidForwarderRPC forwarderRPC = new AndroidForwarderRPC(peer, peerConnection.remotePeer(), bufferConfig, authenticationKey, registrationId, mapUpdateInterval);
    registerRelayForwarder(forwarderRPC);
   
        responder.response(createResponseMessage(message, Type.OK));
    }
   
    private void registerRelayForwarder(BaseRelayForwarderRPC forwarder) {
    for (Commands command : RPC.Commands.values()) {
      if(command == RPC.Commands.RCON) {
        // We must register the rconRPC for every unreachable peer that
        // we serve as a relay. Without this registration, no reverse
        // connection setup is possible.
        dispatcher().registerIoHandler(peer.peerID(), forwarder.unreachablePeerId(), rconRPC, command.getNr());
      } else if (command == RPC.Commands.RELAY) {
        // Register this class to handle all relay messages (currently used when a slow message arrives)
        dispatcher().registerIoHandler(peer.peerID(), forwarder.unreachablePeerId(), this, command.getNr());
      } else {
        dispatcher().registerIoHandler(peer.peerID(), forwarder.unreachablePeerId(), forwarder, command.getNr());
      }
    }
   
    peer.peerBean().addPeerStatusListener(forwarder);
    forwarders.put(forwarder.unreachablePeerId(), forwarder);
  }

    /**
     * The unreachable peer received an envelope message with another message insice (piggypacked)
     */
    private void handlePiggyBackedMessage(Message message, final Responder responderToRelay) throws Exception {
      // TODO: check if we have right setup
       
        // this contains the real sender
        Collection<PeerSocketAddress> peerSocketAddresses = message.peerSocketAddresses();
        final InetSocketAddress sender;
        if(!peerSocketAddresses.isEmpty()) {
      sender = PeerSocketAddress.createSocketTCP(peerSocketAddresses.iterator().next());
        } else {
          sender = new InetSocketAddress(0);
        }
       
        Buffer requestBuffer = message.buffer(0);
        Message realMessage = RelayUtils.decodeRelayedMessage(requestBuffer, message.recipientSocket(), sender, signatureFactory());
        realMessage.restoreContentReferences();

        LOG.debug("Received message from relay peer: {}", realMessage);
       
        final Message envelope = createResponseMessage(message, Type.OK);
        final Responder responder = new Responder() {
          //TODO: add reply leak handler
          @Override
          public void response(Message responseMessage) {
            LOG.debug("Send reply message to relay peer: {}", responseMessage);
            try {
              if(responseMessage.sender().isRelayed() && !responseMessage.sender().peerSocketAddresses().isEmpty()) {
                responseMessage.peerSocketAddresses(responseMessage.sender().peerSocketAddresses());
              }
              envelope.buffer(RelayUtils.encodeMessage(responseMessage, signatureFactory()));
                } catch (Exception e) {
                  LOG.error("Cannot piggyback the response", e);
                  failed(Type.EXCEPTION, e.getMessage());
                }
                responderToRelay.response(envelope);
          }

      @Override
            public void failed(Type type, String reason) {
        responderToRelay.failed(type, reason);
            }

      @Override
            public void responseFireAndForget() {
        responderToRelay.responseFireAndForget();
            }
        };
       
        DispatchHandler dispatchHandler = dispatcher().associatedHandler(realMessage);
        if(dispatchHandler == null) {
          responder.failed(Type.EXCEPTION, "handler not found, probably not relaying peer anymore");
        } else {
          dispatchHandler.handleResponse(realMessage, null, false, responder);
        }
    }

    /**
     * Updates the peer map of an unreachable peer on the relay peer, so that
     * the relay peer can respond to neighbor RPC on behalf of the unreachable
     * peer
     *
     * @param message
     * @param responder
     */
    private void handleMap(Message message, Responder responder) {
      LOG.debug("Handle foreign map update {}", message);
        Collection<PeerAddress> map = message.neighborsSet(0).neighbors();
        BaseRelayForwarderRPC forwarder = forwarders.get(message.sender().peerId());
        if (forwarder != null) {
          forwarder.setPeerMap(RelayUtils.unflatten(map, message.sender()));
        } else {
            LOG.error("No forwarder for peer {} found. Need to setup relay first");
        }
       
        Message response = createResponseMessage(message, Type.OK);
        responder.response(response);
    }
   
    /**
     * The relay buffers messages for unreachable peers (like Android devices). They get notified when the buffer is full
     * or request the buffer content by themselves through this request.
     *
     * @param message
     * @param responder
     */
    private void handleBufferRequest(Message message, Responder responder) {
      LOG.debug("Handle buffer request of unreachable peer {}", message.sender());
    BaseRelayForwarderRPC forwarderRPC = forwarders.get(message.sender().peerId());
    if(forwarderRPC instanceof AndroidForwarderRPC) {
      AndroidForwarderRPC androidForwarder = (AndroidForwarderRPC) forwarderRPC;
     
      try {
        Message response = createResponseMessage(message, Type.OK);
        // add all buffered messages
        response.buffer(androidForwarder.getBufferedMessages());
       
        LOG.debug("Responding all buffered messages to Android device {}", message.sender());
        responder.response(response);
      } catch(Exception e) {
        LOG.error("Cannot respond with buffered messages.", e);
        responder.response(createResponseMessage(message, Type.EXCEPTION));
      }
    } else {
      responder.failed(Type.EXCEPTION, "This message type is intended for buffering forwarders only");
    }
  }
   
  /**
   * There are two possibilites for this case:
   * <ol>
   *   <li>This peer did a request which was now finally answered by a slow peer.</li>
   *   <li>This is the relay peer of the requester which now receives the late response</li>
   * </ol>
   * @param message contains the (piggybacked) response
   * @param responder
   * @param peerConnection
   * @param sign
   */
  private void handleLateResponse(Message message, PeerConnection peerConnection, boolean sign, Responder responder) {
    if(!message.sender().isSlow() ||  message.bufferList().isEmpty()) {
            throw new IllegalArgumentException("Late response does not come from slow peer or does not contain the buffered message");
      }
   
    Message realMessage = null;
    try {
      realMessage = RelayUtils.decodeRelayedMessage(message.buffer(0), message.recipientSocket(),
          message.senderSocket(), signatureFactory());
    } catch (Exception e) {
      LOG.error("Cannot decode the late response", e);
      responder.response(createResponseMessage(message, Type.EXCEPTION));
      return;
    }
   
    LOG.debug("Received late response from slow peer: {}", realMessage);
      // only the case when a unreachable peer makes a request to another slow, unreachable peer
      Map<Integer, FutureResponse> pendingRequests = dispatcher().getPendingRequests();
      FutureResponse pendingRequest = pendingRequests.remove(realMessage.messageId());
      if (pendingRequest != null) {
        // we waited for this response, answer it
        pendingRequest.response(realMessage);

        // send ok, not fire and forget - style
        LOG.debug("Successfully answered pending request {} with {}", pendingRequest.request(), realMessage);
        responder.response(createResponseMessage(message, Type.OK, message.recipient()));
      } else {
        // handle Relayed <--> Relayed.
        // This could be a pending message for one of the relayed peers, not for this peer
        BaseRelayForwarderRPC forwarder = forwarders.get(realMessage.recipient().peerId());
        if (forwarder == null) {
          LOG.error("Forwarder for the relayed peer not found. Cannot send late response {}", realMessage);
          responder.response(createResponseMessage(message, Type.NOT_FOUND));
        } else {
           // because buffer is re-encoded when forwarding it to unreachable
          message.restoreBuffers();
          forwarder.forwardToUnreachable(message);
        }
      }
  }
}
TOP

Related Classes of net.tomp2p.relay.RelayRPC

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.