Package dijjer.io.comm

Source Code of dijjer.io.comm.Dispatcher

/*
* Dijjer - A Peer to Peer HTTP Cache
* Copyright (C) 2004,2005 Change.Tv, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package dijjer.io.comm;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Set;
import dijjer.Dijjer;
import dijjer.io.BlockInfo;
import dijjer.io.download.Download;
import dijjer.io.store.DataStore;
import dijjer.io.store.HashStore;
import dijjer.io.xfer.BlockReceiver;
import dijjer.io.xfer.BlockTransmitter;
import dijjer.io.xfer.InputStreamBlockReceiver;
import dijjer.io.xfer.PacketThrottle;
import dijjer.io.xfer.PartiallyReceivedBlock;
import dijjer.io.xfer.http.HttpBlockReceiver;
import dijjer.util.AbstractThread;
import dijjer.util.Misc;
import dijjer.util.VeryLongInteger;
import dijjer.util.logging.Logger;

/**
* This class handles otherwise unhandled incoming messages and deals with them accordingly
*
* TODO: The way the various different types of request are handled is inconsistent, this should be tidied up
*
* @author ian
*/
public class Dispatcher {

  public static final String VERSION = "$Id: Dispatcher.java,v 1.60 2005/09/14 06:01:14 chronos Exp $";
  private final UdpSocketManager _usm;
  private final RoutingTable _rt;
  private final DataStore _ds;
  private final HashStore _hs;
  private final Set _seedNodes;
  private final HashSet _seenUids = new HashSet();
  private static Dispatcher _disp;

  public Dispatcher() {
    this(UdpSocketManager.getUdpSocketManager(), RoutingTable.getRoutingTable(), DataStore.getDataStore(),
        HashStore.getHashStore(), Dijjer.getDijjer().getSeedNodes());
  }

  public Dispatcher(UdpSocketManager usm, RoutingTable rt, DataStore ds, HashStore hs, Set seedNodes) {
    _usm = usm;
    _usm.setDispatcher(this);
    _rt = rt;
    _ds = ds;
    _hs = hs;
    _seedNodes = seedNodes;
  }

  public boolean handleMessage(final Message m) throws Exception {
    _rt.notifyOfContact(m.getSource());
    if (m.getSpec().equals(DMT.ping)) {
      _usm.send(m.getSource(), DMT.createPong(m));
      return true;
    } else if (m.getSpec().equals(DMT.pong)) {
      if (m.isSet(DMT.SEND_TIME)) {
        PacketThrottle.getThrottle(m.getSource()).setRoundTripTime(
            System.currentTimeMillis() - m.getLong(DMT.SEND_TIME));
      }
      if (_rt.getPeerInfo(m.getSource()) == null) {
        _usm.send(m.getSource(), DMT.createWhoAreYou());
      }
      return true;
    } else if (m.getSpec().equals(DMT.introduce)) {
      Peer externalAddress = (Peer) m.getObject(DMT.EXTERNAL_ADDRESS);
      int build = m.getInt(DMT.BUILD);
      int firstGoodBuild = m.getInt(DMT.FIRST_GOOD_BUILD);
      _rt.setPeer(externalAddress);
      _rt.setPeerInfo(m.getSource(), new RoutingTable.PeerInfo(build, firstGoodBuild, externalAddress));
      return true;
    } else if (m.getSpec().equals(DMT.whoAreYou)) {
      _usm.send(m.getSource(), DMT.createIntroduce(m.getSource()));
      return true;
    } else if (m.getSpec().equals(DMT.joinRequest)) {
      final Integer uid = new Integer(m.getInt(DMT.UID));
      if (!_seenUids.contains(uid)) {
        _seenUids.add(uid);
        new AbstractThread() {

          protected boolean loop() throws InterruptedException {
            handleJoinRequest(m);
            _seenUids.remove(uid);
            return false;
          }

          protected void cleanUp() {
          }
        }.startThread();
      } else {
        _usm.send(m.getSource(), DMT.createRejectDueToLoop(m.getInt(DMT.UID)));
      }
      return true;
    } else if (m.getSpec().equals(DMT.requestData)) {
      final Integer uid = new Integer(m.getInt(DMT.UID));
      if (!_seenUids.contains(uid)) {
        _seenUids.add(uid);
        new AbstractThread() {

          protected boolean loop() throws InterruptedException {
            try {
              _usm.send(m.getSource(), DMT.createAcknowledgeRequest(m.getInt(DMT.UID)));
              PartiallyReceivedBlock prb = new PartiallyReceivedBlock(Dijjer.PACKETS_IN_BLOCK,
                  Dijjer.PACKET_SIZE);
              retrieveData(new BlockInfo(m), m.getInt(DMT.TTL), m.getSource(), m.getInt(DMT.UID),
                  (LinkedList) m.getObject(DMT.FORWARDERS), prb, null);
              _seenUids.remove(uid);
            } catch (Exception e) {
              Logger.warning("Error while retrieving data for another peer", e);
            }
            return false;
          }

          protected void cleanUp() {
          }
        }.startThread();
      } else {
        _usm.send(m.getSource(), DMT.createRejectDueToLoop(m.getInt(DMT.UID)));
      }
      return true;
    } else if (m.getSpec().equals(DMT.requestHash)) {
      final Integer uid = new Integer(m.getInt(DMT.UID));
      if (!_seenUids.contains(uid)) {
        _seenUids.add(uid);
        new AbstractThread() {

          public boolean loop() {
            try {
              _usm.send(m.getSource(), DMT.createRequestHashAck(m.getInt(DMT.UID)));
              VeryLongInteger hash = retrieveHash(new BlockInfo(m), m.getInt(DMT.TTL), m.getInt(DMT.UID),
                  true);
              _usm.send(m.getSource(), DMT.createReplyHash(m.getInt(DMT.UID), hash));
              _seenUids.remove(uid);
            } catch (Exception e) {
              Logger.error(e);
              throw new RuntimeException(e);
            }
            return false;
          }
        }.startThread();
      } else {
        _usm.send(m.getSource(), DMT.createRejectDueToLoop(m.getInt(DMT.UID)));
      }
      return true;
    } else if (m.getSpec().equals(DMT.corruptionNotification)) {
      final Integer uid = new Integer(m.getInt(DMT.UID));
      if (!_seenUids.contains(uid)) { // We don't bother with the rejectDueToLoop
        _seenUids.add(uid);
        new AbstractThread() {

          public boolean loop() {
            try {
              handleCorruptionNotification(new BlockInfo(m), m.getInt(DMT.UID), m.getSource(), m
                  .getBoolean(DMT.IS_HASH));
              _seenUids.remove(uid);
            } catch (Exception e) {
              Logger.error(e);
              throw new RuntimeException(e);
            }
            return false;
          }
        }.startThread();
      }
      return true;
    } else {
      return false;
    }
  }

  public void registerUid(int uid) {
    _seenUids.add(new Integer(uid));
  }

  public void unregisterUid(int uid) {
    _seenUids.remove(new Integer(uid));
  }

  public boolean retrieveData(BlockInfo bi, int ttl, PartiallyReceivedBlock prb, Download dl) throws IOException,
      RetrievalException {
    int uid = Misc.nextInt();
    registerUid(uid);
    boolean ret = retrieveData(bi, ttl, null, uid, new LinkedList(), prb, dl);
    unregisterUid(uid);
    return ret;
  }

  public boolean retrieveData(final BlockInfo bi, int ttl, final Peer requestor, final int uid,
      final LinkedList forwarders, final PartiallyReceivedBlock prb, Download download) throws IOException,
      RetrievalException {
    boolean cached = false;
    Logger.info("Dispatcher: Retrieving " + bi + " at ttl " + ttl);
    // See if we have it in our local store
    byte[] data = _ds.getDataForBlock(bi.getHashKey());
    if (data != null) {
      (new InputStreamBlockReceiver(prb, new ByteArrayInputStream(data))).start();
      if (requestor != null) {
        forwardData(requestor, prb, uid, forwarders, true);
      }
      return true;
    }
    Peer best = null;
    Peer last = null;
    HashSet exclude = new HashSet(); // We don't want to bother the seed with
    // requests for data
    exclude.addAll(_seedNodes);
    exclude.addAll(forwarders);
    while (true) { // We loop until we get the data or give up
      best = _rt.findClosest(bi.getHashKey(), exclude);
      // If the ttl has run out, there is nowhere to route, or if the next hop is further away in hash-space from
      // the data, we download it ourselves.
      // TODO: Make this robust against download failure
      if ((ttl < 1) || (best == null) || bi.getHashKey().closerTo(_rt.getPeer().getHash(), best.getHash())) {
        HttpBlockReceiver hbr = new HttpBlockReceiver(bi, prb);
        if (requestor != null) {
          new Thread() {

            public void run() {
              forwardData(requestor, prb, uid, forwarders, false);
              /*
               * Only add the block to the DS if we didn't request it, this helps to prevent the datastore
               * getting "flushed" every time we do a request
               */
              try {
                _ds.addDataAsBlock(bi.getHashKey(), prb.getBlock());
              } catch (IOException e) {
                throw new RuntimeException(e);
              }
            }
          }.start();
        }
        hbr.start();
        return false;
      }
      // Ok, we are going to ask another peer for the data, first construct our request
      // And send it to our best guess
      if (!forwarders.contains(_rt.getPeer())) {
        forwarders.add(_rt.getPeer());
      }
      _usm.send(best, DMT.createRequestData(uid, bi, forwarders, ttl - 1));
      // Now we expect an Ack, give it 4 seconds
      Message ar = _usm.waitFor(MessageFilter.create(4000, DMT.acknowledgeRequest).addType(DMT.rejectDueToLoop)
          .setField(DMT.UID, uid));
      if (ar == null) {
        // No response in 4 seconds, flag this guy to be recontacted and continue the while loop to find another
        // or possibly just download it ourselves
        if (best == last) exclude.add(best);
        _rt.recontactPeer(best);
        exclude.add(best);
        continue;
      }
      if (ar.getSpec().equals(DMT.rejectDueToLoop)) {
        Logger.warning("requestData (uid: " + uid + ") rejected by " + best + " due to loop");
        exclude.add(best);
        continue;
      }
      // Got the ack, we give it 5 minutes to account for the time it might take a peer to download it from
      // a slow server
      MessageFilter succFilt = MessageFilter.create(30000, DMT.requestSuccessful).addType(DMT.requestFailed)
          .setSource(best).setField(DMT.UID, uid);
      final Message resp = _usm.waitFor(succFilt);
      if ((resp == null) || (resp.getSpec().equals(DMT.requestFailed))) {
        if (resp == null) {
          if (requestor != null) {
            _usm.send(requestor, DMT.createRequestFailed(uid, RetrievalException.TIMED_OUT,
                "Timed out waiting for requestSuccessful"));
          }
          throw new RetrievalException(RetrievalException.TIMED_OUT,
              "Timed out waiting on requestSuccessful for block " + bi.getBlockNo());
        } else {
          if (requestor != null) {
            _usm.send(requestor, DMT.createRequestFailed(uid, (resp.isSet(DMT.REASON) ? resp
                .getInt(DMT.REASON) : RetrievalException.UNKNOWN), (resp.isSet(DMT.DESCRIPTION) ? resp
                .getString(DMT.DESCRIPTION) : "Unknown")));
          }
          throw new RetrievalException((resp.isSet(DMT.REASON)
              ? resp.getInt(DMT.REASON)
              : RetrievalException.UNKNOWN), (resp.isSet(DMT.DESCRIPTION) ? resp
              .getString(DMT.DESCRIPTION) : "Unknown"));
        }
      }
      // Ok, the data has been retrieved and should be on its way
      if (requestor != null) {
        (new Thread() {

          public void run() {
            forwardData(requestor, prb, uid, forwarders, resp.getBoolean(DMT.CACHED));
            if (prb.allReceived()) {
              try {
                _ds.addDataAsBlock(bi.getHashKey(), prb.getBlock());
              } catch (IOException e) {
                Logger.error("Error writing data to datastore", e);
              }
            }
          }
        }).start();
        // We add the new peer to our RT *after* the transfer as this is when it will start trying to contact us
        _rt.addPeer((Peer) resp.getObject(DMT.DATA_SOURCE));
        // Note that we only add the data to our DS if someone else requested it from us
      }
      BlockReceiver br = new BlockReceiver(_usm, best, uid, prb);
      br.receive();
      return resp.getBoolean(DMT.CACHED);
    }
  }

  protected void forwardData(Peer dest, PartiallyReceivedBlock prb, int uid, LinkedList forwarders, boolean wasCached) {
    _usm.send(dest, DMT.createRequestSuccessful(uid, _rt.getPeer(), wasCached));
    BlockTransmitter bt = new BlockTransmitter(_usm, dest, uid, prb);
    bt.send();
    synchronized (forwarders) {
      for (Iterator i = forwarders.iterator(); i.hasNext();) {
        Peer p = (Peer) i.next();
        _rt.addPeer(p);
      }
    }
  }

  protected void handleJoinRequest(Message jr) {
    int ttl = jr.getInt(DMT.TTL);
    int uid = jr.getInt(DMT.UID);
    Peer joiner = (Peer) jr.getObject(DMT.JOINER);
    if (joiner.isNull()) {
      joiner = jr.getSource();
    }
    if (joiner.equals(_rt.getPeer())) {
      Logger.warning("Received join request from self!  Ignoring");
      return;
    }
    _usm.send(jr.getSource(), DMT.createJoinRequestAck(uid));
    HashSet dontConsider = new HashSet();
    // Don't route to the joiner, or the guy who sent it to us
    dontConsider.add(joiner);
    dontConsider.add(jr.getSource());
    LinkedList exclude;
    if (jr.isSet(DMT.EXCLUDE)) {
      exclude = (LinkedList) jr.getObject(DMT.EXCLUDE);
      dontConsider.add(exclude);
    } else {
      exclude = new LinkedList();
    }
    while (true) {
      Logger.info("LOOP: Forward joinRequest");
      Peer best = _rt.findClosest(joiner.getHash(), dontConsider);
      /*
       * Terminate request and respond if: 1) TTL has expired 2) We couldn't find anywhere to route to
       */
      if ((ttl == 0) || (best == null) || ttl < Dijjer.JOINTTL) {
        LinkedList peers = new LinkedList();
        peers.add(_rt.getPeer());
        _rt.addPeer(joiner);
        _usm.send(jr.getSource(), DMT.createJoinResponse(uid, peers));
        break;
      }
      exclude.add(new Integer(RoutingTable.getRoutingTable().getPeer().hashCode()));
      _usm.send(best, DMT.createJoinRequest(uid, joiner, jr.getInt(DMT.TTL) - 1, exclude));
      Message jra = _usm.waitFor(MessageFilter.create(6000, DMT.joinRequestAck).addType(DMT.rejectDueToLoop)
          .setField(DMT.UID, uid));
      if (jra == null) {
        _rt.recontactPeer(best);
        continue;
      }
      if (jra.getSpec().equals(DMT.rejectDueToLoop)) {
        dontConsider.add(best);
        Logger.warning("joinRequest (uid: " + uid + ") rejected by " + jra.getSource() + " due to loop");
        continue;
      }
      Message jrs = _usm.waitFor(MessageFilter.create(1000 * (ttl + 3), DMT.joinResponse).setSource(best)
          .setField(DMT.UID, uid));
      if (jrs == null) {
        LinkedList peers = new LinkedList();
        peers.add(_rt.getPeer());
        _rt.addPeer(joiner);
        _usm.send(jr.getSource(), DMT.createJoinResponse(uid, peers));
        break;
      } else {
        LinkedList peers = (LinkedList) jrs.getObject(DMT.PEERS);
        if (_rt.getPeers().size() < _rt.getMaxSize()) {
          peers.add(_rt.getPeer());
          _rt.addPeer(joiner);
        }
        // If any are null assume they are the sender of the joinResponse
        for (ListIterator i = peers.listIterator(); i.hasNext();) {
          Peer p = (Peer) i.next();
          if (p.isNull()) {
            i.remove();
          }
        }
        _usm.send(jr.getSource(), DMT.createJoinResponse(uid, peers));
        break;
      }
    }
  }

  public VeryLongInteger retrieveHash(final BlockInfo bi, int ttl, int uid, boolean cacheResult) throws IOException,
      RetrievalException {
    VeryLongInteger lo = _hs.getHash(bi.getHashHashKey());
    if (lo != null) {
      return lo;
    }
    HashSet exclude = new HashSet(); // We don't want to bother the seeds with
    // requests for hashes
    exclude.addAll(_seedNodes);
    while (true) {
      Peer best = _rt.findClosest(bi.getHashHashKey(), exclude);
      if ((ttl == 0) || (best == null) || (bi.getHashHashKey().closerTo(_rt.getPeer().getHash(), best.getHash()))) {
        // We are going to download and retrieve it ourselves
        VeryLongInteger dataHash = retrieveHashFromServer(bi);
        _hs.put(bi.getHashHashKey(), dataHash);
        return dataHash;
      }
      // Ok, we are going to try forwarding it to the next closest peer
      _usm.send(best, DMT.createRequestHash(uid, bi, ttl - 1));
      Message requestHashAck = _usm.waitFor(MessageFilter.create(5000, DMT.requestHashAck).addType(
          DMT.rejectDueToLoop).setField(DMT.UID, uid));
      if (requestHashAck == null) {
        exclude.add(best);
        _rt.recontactPeer(best);
        continue;
      }
      if (requestHashAck.getSpec().equals(DMT.rejectDueToLoop)) {
        Logger.warning("requestHash (uid: " + uid + ") rejected by " + requestHashAck.getSource()
            + " due to loop");
        exclude.add(best);
        continue;
      }
      Message replyHash = _usm.waitFor(MessageFilter.create(30000, DMT.replyHash).setField(DMT.UID, uid));
      if (replyHash == null) {
        Logger.info("Timed out waiting for replyHash from " + best);
        ttl = 0;
        continue;
      }
      VeryLongInteger dataHash = (VeryLongInteger) replyHash.getObject(DMT.HASH);
      if (cacheResult) {
        _hs.put(bi.getHashHashKey(), dataHash);
      }
      return dataHash;
    }
  }

  private void handleCorruptionNotification(BlockInfo bi, int uid, Peer source, boolean forHash) {
    try {
      // Ok, first lets see if its valid
      if (forHash) {
        VeryLongInteger myHash = _hs.getHash(bi.getHashHashKey());
        if (myHash == null) {
          Logger.info("Ignoring corruptionNotification because we don't have the hash in our HashStore");
          return;
        }
        VeryLongInteger actualHash = retrieveHashFromServer(bi);
        if (actualHash.equals(myHash)) {
          Logger.warning("Received bogus corruptionNotification from " + source + ", dropping from RT");
          LinkedList r = new LinkedList();
          r.add(source);
          _rt.removePeers(r, "You sent a bogus corruptionNotification");
          return;
        }
        _hs.delete(bi.getHashHashKey());
      } else {
        byte[] localData = _ds.getDataForBlock(bi.getHashKey());
        if (localData == null) {
          Logger.info("Ignoring corruptionNotification because we don't have the data in our DataStore");
          return;
        }
        VeryLongInteger myHash = new VeryLongInteger(localData);
        PartiallyReceivedBlock prb = new PartiallyReceivedBlock(Dijjer.PACKETS_IN_BLOCK, Dijjer.PACKET_SIZE);
        (new HttpBlockReceiver(bi, prb)).start();
        VeryLongInteger actualHash = new VeryLongInteger(prb.getBlock());
        if (actualHash.equals(myHash)) {
          Logger.warning("Received bogus corruptionNotification from " + source + ", dropping from RT");
          LinkedList r = new LinkedList();
          r.add(source);
          _rt.removePeers(r, "You sent a bogus corruptionNotification");
          return;
        }
        _ds.delete(bi.getHashKey());
      }
      // It is valid, broadcast it to other peers
      for (Iterator i = ((ArrayList) _rt.getPeers().clone()).iterator(); i.hasNext();) {
        Peer p = (Peer) i.next();
        if (!p.equals(source)) {
          _usm.send(p, DMT.createCorruptionNotification(uid, bi, forHash));
        }
      }
    } catch (Exception e) {
      Logger.warning("Exception while processing corruptionNotification", e);
    }
  }

  protected VeryLongInteger retrieveHashFromServer(BlockInfo bi) throws RetrievalException {
    PartiallyReceivedBlock prb = new PartiallyReceivedBlock(Dijjer.PACKETS_IN_BLOCK, Dijjer.PACKET_SIZE);
    HttpBlockReceiver hbr = new HttpBlockReceiver(bi, prb);
    try {
      hbr.start();
    } catch (IOException e) {
      throw new RetrievalException(RetrievalException.IO_ERROR, e.getMessage());
    }
    return new VeryLongInteger(prb.getBlock());
  }

  public RoutingTable getRoutingTable() {
    return _rt;
  }

  public DataStore getDataStore() {
    return _ds;
  }

  public HashStore getHashStore() {
    return _hs;
  }

  public static void init() {
    _disp = new Dispatcher();
  }

  public static Dispatcher getDispatcher() {
    return _disp;
  }
}
TOP

Related Classes of dijjer.io.comm.Dispatcher

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.