package platform.plugins.installables.network.routing;
import util.Parameters;
import platform.servicesregister.ServicesRegisterManager;
import platform.servicesregister.ServiceInUseException;
import platform.servicesregister.ServiceClosedException;
import java.util.Vector;
import model.interfaces.platform.IPlatformPlugin;
import java.util.TimerTask;
import java.util.Timer;
import util.NetworkAddress;
import network.platform.PingService;
import platform.plugins.PlatformPluginsLauncher;
import network.NetworkEmitterContainer;
import network.NetworkReceptorContainer;
import network.platform.NetworkPlatformMessage;
import network.platform.PlatformMessagesReceptor;
import network.AddressesChecker;
import platform.plugins.installables.network.DNS.KalimuchoDNS;
/**
* Service for finding a route to a given host.<br>
* This service starts by trying a ping to the host.<br>
* If the ping does not reply then it uses a broadcast/multicast message.<br>
* The route is limited to one hop (only a relay between two hosts) because Kalimucho only create on hops relays connectors.<br>
* In reality it is not realing a routing service as usually used in networks but a proxy service.
* The aim is to find an host that can create a link between two differents networks (IP and zigbee for example).
* @author Dalmau
*/
public class RoutingService extends Thread implements IPlatformPlugin, IRoutingService {
private Vector<NetworkPlatformMessage> boiteALettres; // stocke les reponses recues
private boolean fin; // pour terminer lorsqu'il n'y a pas de route
private int tentatives; // nombre de tentatives de recherche de route
private PingService ping;
private NetworkReceptorContainer rec;
private NetworkEmitterContainer nec;
private PlatformMessagesReceptor boiteDeReponses;
private AddressesChecker addressesChecker;
private long debutRecherche;
private boolean actif = false;
private Object semaphore;
/**
* Create the routing service
*/
public RoutingService() {
boiteALettres = new Vector<NetworkPlatformMessage>();
nec = (NetworkEmitterContainer)ServicesRegisterManager.platformWaitForService(Parameters.NETWORK_EMISSIONS_CONTAINER);
rec = (NetworkReceptorContainer)ServicesRegisterManager.platformWaitForService(Parameters.NETWORK_RECEPTIONS_CONTAINER);
boiteDeReponses = rec.getPlatformMessagesReceptor();
semaphore = new Object();
addressesChecker = (AddressesChecker)ServicesRegisterManager.platformWaitForService(Parameters.NETWORK_ADDRESSES);
// enregistrer le service de routage
try {
ServicesRegisterManager.registerService(Parameters.NETWORK_ROUTING_SERVICE, this);
try {
ping = (PingService)ServicesRegisterManager.lookForService(Parameters.NETWORK_PING_SERVICE);
}
catch (ServiceClosedException sce) {
PlatformPluginsLauncher lanceur = (PlatformPluginsLauncher)ServicesRegisterManager.platformWaitForService(Parameters.PLUGINS_LAUNCHER);
String plug = PingService.class.getName();
try {
lanceur.installPlugin(plug);
}
catch (ClassNotFoundException cnfe) {
System.err.println("Can't start plugin "+plug+" : class unknown");
}
catch (InstantiationException cnfe) {
System.err.println("Can't start plugin "+plug+" : class can't be instantiated");
}
catch (IllegalAccessException cnfe) {
System.err.println("Can't start plugin "+plug+" : Illegal access to the class");
}
}
}
catch (ServiceInUseException mbiue) {
System.err.println("IP neighborhood created twice");
}
}
/**
* Starts the plugin
*/
public void startPlugin() {
if (actif) return;
actif = true;
ping = (PingService)ServicesRegisterManager.platformWaitForService(Parameters.NETWORK_PING_SERVICE);
boiteDeReponses.inscription(Parameters.NETWORK_ROUTING_SERVICE);
start();
}
/**
* Stops the server that receives broadcast messages and the server thar receives replies to these broadcast messages
*/
public void stopPlugin() {
if (!actif) return;
actif = false;
try {
ServicesRegisterManager.removeService(Parameters.NETWORK_ROUTING_SERVICE);
}
catch (ServiceClosedException sce) {}
boiteDeReponses.stop();
boiteDeReponses.desinscription(Parameters.NETWORK_ROUTING_SERVICE);
}
/**
* Depose a reply message to send
* @param message reply message to send
*/
public synchronized void depose(NetworkPlatformMessage message) {
// depot d'une reponse recue au routage
boiteALettres.addElement(message);
notifyAll();
}
private synchronized NetworkPlatformMessage retire(NetworkAddress toFind) {
// retrait d'une reponse au routage
boolean trouve = false;
NetworkPlatformMessage recu = null;
try {
while (!trouve) { // attente de reponse a la question posee
while ((boiteALettres.size() == 0) && (!fin)) {
wait(); // attente d'une reponse
}
if (!fin) { // le delai d'attente n'est pas ecoule
recu = boiteALettres.firstElement();
boiteALettres.removeElementAt(0);
// verifier qu'il s'agisse bien d'une reponse a la question posee
// Ce test est necessaire car des reponses a une question anterieure
// peuvent encore arriver.
String[] parties = recu.getContent().split(";");
if (toFind.equals(new NetworkAddress(parties[0]))) trouve = true;
else recu = null;
}
else trouve = true; // on s'arrete faute de reponse
}
if (recu != null) {
try {
KalimuchoDNS dns = (KalimuchoDNS)ServicesRegisterManager.lookForService(Parameters.KALIMUCHO_DNS_MANAGER);
long t1 = System.currentTimeMillis();
long tDistant = recu.getDate();
long delta = ((debutRecherche + t1)/2) - tDistant; // valeur a ajouter a l'horloge distante pour avoir l'heure locale
dns.adjustRemoteHostClockShift(recu.getSenderID(), delta, t1-debutRecherche);
}
catch (ServiceClosedException nfe) {}
}
return recu; // route recue ou null si fin du delai
}
catch (InterruptedException ie) {
return null;
}
}
/**
* Performs the treatment of received messages for finding a route
* @param message received message
*/
private void traiterDemandes(NetworkPlatformMessage message) {
String[] parties = message.getContent().split(";");
NetworkAddress who = new NetworkAddress(parties[1]);
NetworkAddress what = new NetworkAddress(parties[0]);
NetworkAddress destinataire = new NetworkAddress(message.getExpeditorAddress());
NetworkAddress monAdr = addressesChecker.getMyFirstAddress(who.getType());
if (addressesChecker.isPresentAddress(what)) { // la route demandee est vers moi
if (who.equals(destinataire)) {
// repondre au demandeur en indiquant que la route est directe
NetworkPlatformMessage reponse = new NetworkPlatformMessage();
reponse.setFinalAddress("local"); // on repond directement au demandeur
reponse.setFinalPort(0);
reponse.addContent(message.getContent()+";"+monAdr.getNormalizedAddress()+";direct");
reponse.setAddress(destinataire.getNormalizedAddress()); // on repond directement au demandeur
reponse.setPortNumber(0);
reponse.setOwner(Parameters.NETWORK_ROUTING_SERVICE);
reponse.setReplyTo(Parameters.NETWORK_ROUTING_SERVICE);
//System.out.println("Sending reply to route: "+reponse.getContent()+" for "+destinataire.getNormalizedAddress()+" to "+reponse.getFinalAddress()+" via "+reponse.getAddress());
nec.getPlatformMessagesEmitter().postDirectMessage(reponse);
}
else {
// repondre au demandeur en indiquant que la route est indirecte et que
// l'hote par lequel ce message est arrive sert de relai
NetworkPlatformMessage reponse = new NetworkPlatformMessage();
reponse.setFinalAddress(who.getNormalizedAddress()); // on repond au demandeur
reponse.setFinalPort(0);
reponse.addContent(message.getContent()+";"+destinataire.getNormalizedAddress()+";indirect");
reponse.setAddress(destinataire.getNormalizedAddress()); // on repond via l'hote par lequel ce message est arrive
reponse.setPortNumber(0);
reponse.setOwner(Parameters.NETWORK_ROUTING_SERVICE);
reponse.setReplyTo(Parameters.NETWORK_ROUTING_SERVICE);
//System.out.println("Sending reply to route: "+reponse.getContent()+" for "+destinataire.getNormalizedAddress()+" to "+reponse.getFinalAddress()+" via "+reponse.getAddress());
nec.getPlatformMessagesEmitter().postDirectMessage(reponse);
}
}
else {
if (who.equals(destinataire)) { // La demande est parvenue directement et l'@ recherche n'est pas celle de cet hote
// On va voir si on peut servir de relai, dans le cas contraire on renvoie la demande en broadcast
if (ping.testConnexionTo(what)) { // on a trouve la route vers l'hote recherche par PING
// repondre au demandeur en indiquant que la route est indirecte et que
// cet hote peut servir de relai
NetworkPlatformMessage reponse = new NetworkPlatformMessage();
if (who.equals(destinataire)) reponse.setFinalAddress("local"); // la reponse se fait directement car la demande est arrivee directement
else reponse.setFinalAddress(who.getNormalizedAddress()); // la reponse se fait via l'hote par lequel cette demande nous est parvenue
reponse.setFinalPort(0);
reponse.addContent(message.getContent()+";"+monAdr.getNormalizedAddress()+";indirect");
reponse.setAddress(destinataire.getNormalizedAddress()); // on envoie la reponse a l'hote par lequel cette demande nous est parvenue
reponse.setPortNumber(0);
reponse.setOwner(Parameters.NETWORK_ROUTING_SERVICE);
reponse.setReplyTo(Parameters.NETWORK_ROUTING_SERVICE);
//System.out.println("Sending reply to route: "+reponse.getContent()+" for "+destinataire.getNormalizedAddress()+" to "+reponse.getFinalAddress()+" via "+reponse.getAddress());
nec.getPlatformMessagesEmitter().postDirectMessage(reponse);
}
else { // on ne peut pas atteindre l'hote recherche directement
message.setExpeditorAdressWhenSending();
nec.getPlatformMessagesEmitter().postBroadcastMessage(message); // on redifuse en broadcast
}
} // les messages pour lesquels on n'a pas de reponse et qui ne nous sont pas parvenu directement sont ignores
}
}
/**
* Find a route
* @param vers to which host
* @return the founded route
* @throws NoRouteException if there is no route
*/
public ReplyForRouteMessage findRoute(NetworkAddress vers) throws NoRouteException {
synchronized(semaphore) {
debutRecherche = System.currentTimeMillis();
// recherche d'une route vers l'@ passee en parametre
if (addressesChecker.isPresentAddress(vers)) return new ReplyForRouteMessage(vers.getNormalizedAddress(), vers.getNormalizedAddress(), vers.getNormalizedAddress(), "direct");
System.out.println("Try finding route to: "+vers.getNormalizedAddress()+" with PING");
if (ping.testConnexionTo(vers)) { // on a trouve la route directement
ReplyForRouteMessage recu = new ReplyForRouteMessage(addressesChecker.getMyFirstAddress(vers.getType()).getNormalizedAddress(), vers.getNormalizedAddress(), vers.getNormalizedAddress(), "direct");
System.out.println("Ping found a direct route to: "+vers.getNormalizedAddress());
//System.out.println("TPS recherche route par ping : "+(System.currentTimeMillis()-debutRecherche));
return recu;
}
System.out.println("Try finding route to: "+vers.getNormalizedAddress()+" with broadcast");
// Si ping n'a pas marche on va s'adresser au serveur pour trouver la route
boiteALettres.removeAllElements(); // virer les reponses anterieures
Timer delai = new Timer(); // timer pour eviter d'attendre trop longtemps
NetworkPlatformMessage recu = null;
boolean trouve = false; // pas de reponse pour le moment
tentatives = 0;
while ((tentatives < Parameters.NUMBER_RETRIES_FOR_ROUTE) && (!trouve)) { // faire une tentative
// Envoi de la requete sur chaque reseau
NetworkPlatformMessage envoi = new NetworkPlatformMessage();
envoi.setFinalAddress("local");
envoi.addContent(vers.getNormalizedAddress()+";");
envoi.setOwner(Parameters.NETWORK_ROUTING_SERVICE);
envoi.setReplyTo(Parameters.NETWORK_ROUTING_SERVICE);
envoi.setExpeditorAdressWhenSending();
debutRecherche = System.currentTimeMillis();
nec.getPlatformMessagesEmitter().postBroadcastIncompleteMessage(envoi);
// On allonge le temps d'attente a chaque nouvelle tentative
delai.schedule(new WaitForReply(), Parameters.MAXIMAL_WAIT_FOR_RETRY_ROUTE * (tentatives+1));
fin = false; // le delai d'attente n'est pas ecoule
recu = null;
while ((!fin) && (!trouve)) { // attente reponse ou fon de delai
recu = retire(vers); // retirer la reponse
if (recu != null) trouve = true; // on a trouve une route
delai.cancel(); // arreter le timer
}
if (!trouve) { // reessayer une fois
delai = new Timer();
tentatives++;
if (tentatives < Parameters.NUMBER_RETRIES_FOR_ROUTE) {
System.out.println("Retrying finding route to: "+vers.getNormalizedAddress());
}
}
}
if (trouve) { // on a obtenu une reponse => route trouvee
String[] parties = recu.getContent().split(";");
ReplyForRouteMessage reponse = new ReplyForRouteMessage(parties[1], parties[0], parties[2], parties[3]);
System.out.println("Found route to: "+vers.getNormalizedAddress()+" via: "+reponse.getVia());
//System.out.println("TPS recherche route par broadcast : "+(System.currentTimeMillis()-debutRecherche));
return reponse; // renvoyer la route trouvee
}
else { // si pas de route apres n tentatives lever une exception
throw new NoRouteException();
}
}
}
/**
* Waits for messages concerning the routes.<br>
* When a message asking for a route is received, the thread tries to reply to it:<br>
* - a direct route exists if this host is the one for which a route is requested and it receives directly this request<br>
* - an indirect route exists if this host is the one for which a route is requested and it receives indirectly this request<br>
* - an indirect route exists if this host is not the one for which a route is requested and it can reach the requested host<br>
* If it can reply, it relays this request.
* When a message answering to a route is received, the thread unlock the route requester.
*
*/
@Override
public void run() {
while (actif) {
NetworkPlatformMessage recu = boiteDeReponses.retirerMessage(Parameters.NETWORK_ROUTING_SERVICE);
if (recu != null) {
//System.out.println("*** Routage a recu : "+recu.getContent()+" de "+recu.getExpeditorAddress());
String msg = recu.getContent();
if (!msg.contains("direct")) { // message de demande
traiterDemandes(recu);
}
else { // message de reponse
depose(recu);
}
}
}
}
private synchronized void arreter() {
notifyAll(); // debloquer le thread qui attend une route
}
private class WaitForReply extends TimerTask {
public void run() { // le delai d'attente est passe
fin = true;
arreter(); // debloquer le thread demandeur
}
}
}