Beispiel #1
0
 /**
  * Bytes not yet in storage. Does NOT account for skipped files.
  *
  * @return exact value. or -1 if no storage yet. getNeeded() * pieceLength(0) isn't accurate if
  *     last piece is still needed.
  * @since 0.8.9
  */
 public long getRemainingLength() {
   if (meta != null && storage != null) {
     long needed = storage.needed();
     long length0 = meta.getPieceLength(0);
     long remaining = needed * length0;
     // fixup if last piece is needed
     int last = meta.getPieces() - 1;
     if (last != 0 && !storage.getBitField().get(last))
       remaining -= length0 - meta.getPieceLength(last);
     return remaining;
   }
   return -1;
 }
Beispiel #2
0
 /**
  * Switch from magnet mode to normal mode. If we already have the metainfo, this does nothing.
  *
  * @param meta non-null
  * @since 0.8.4
  */
 public void setMetaInfo(MetaInfo meta) {
   if (metainfo != null) return;
   BitField oldBF = bitfield;
   if (oldBF != null) {
     if (oldBF.size() != meta.getPieces())
       // fix bitfield, it was too big by 1-7 bits
       bitfield = new BitField(oldBF.getFieldBytes(), meta.getPieces());
     // else no extra
   } else {
     // it will be initialized later
     // bitfield = new BitField(meta.getPieces());
   }
   metainfo = meta;
   if (bitfield != null && bitfield.count() > 0) setInteresting(true);
 }
Beispiel #3
0
  void bitfieldMessage(byte[] bitmap) {
    synchronized (this) {
      if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " rcv bitfield");
      if (bitfield != null) {
        // XXX - Be liberal in what you accept?
        if (_log.shouldLog(Log.WARN)) _log.warn("Got unexpected bitfield message from " + peer);
        return;
      }

      // XXX - Check for weird bitfield and disconnect?
      // FIXME will have to regenerate the bitfield after we know exactly
      // how many pieces there are, as we don't know how many spare bits there are.
      if (metainfo == null) bitfield = new BitField(bitmap, bitmap.length * 8);
      else bitfield = new BitField(bitmap, metainfo.getPieces());
    }
    if (metainfo == null) return;
    boolean interest = listener.gotBitField(peer, bitfield);
    setInteresting(interest);
    if (bitfield.complete() && !interest) {
      // They are seeding and we are seeding,
      // why did they contact us? (robert)
      // Dump them quick before we send our whole bitmap
      if (_log.shouldLog(Log.WARN)) _log.warn("Disconnecting seed that connects to seeds: " + peer);
      peer.disconnect(true);
    }
  }
Beispiel #4
0
 /**
  * Called when the PeerCoordinator got the MetaInfo via magnet. CoordinatorListener. Create the
  * storage, tell SnarkManager, and give the storage back to the coordinator.
  *
  * @throws RuntimeException via fatal()
  * @since 0.8.4
  */
 public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
   try {
     String base = Storage.filterName(metainfo.getName());
     File baseFile;
     if (_util.getFilesPublic()) baseFile = new File(rootDataDir, base);
     else baseFile = new SecureFile(rootDataDir, base);
     // The following two may throw IOE...
     storage = new Storage(_util, baseFile, metainfo, this, false);
     storage.check();
     // ... so don't set meta until here
     meta = metainfo;
     if (completeListener != null) {
       String newName = completeListener.gotMetaInfo(this);
       if (newName != null) torrent = newName;
       // else some horrible problem
     }
     coordinator.setStorage(storage);
   } catch (IOException ioe) {
     if (storage != null) {
       try {
         storage.close();
       } catch (IOException ioee) {
       }
     }
     fatal("Could not check or create storage", ioe);
   }
 }
Beispiel #5
0
 /**
  * Does not account (i.e. includes) for skipped files.
  *
  * @return number of pieces still needed (magnet mode or not), or -1 if unknown
  * @since 0.8.4
  */
 public long getNeeded() {
   if (storage != null) return storage.needed();
   if (meta != null)
     // FIXME subtract chunks we have
     return meta.getTotalLength();
   // FIXME fake
   return -1;
 }
Beispiel #6
0
  void requestMessage(int piece, int begin, int length) {
    if (_log.shouldLog(Log.DEBUG))
      _log.debug(peer + " rcv request(" + piece + ", " + begin + ", " + length + ") ");
    if (metainfo == null) return;
    if (choking) {
      if (_log.shouldLog(Log.INFO)) _log.info("Request received, but choking " + peer);
      return;
    }

    // Sanity check
    if (piece < 0
        || piece >= metainfo.getPieces()
        || begin < 0
        || begin > metainfo.getPieceLength(piece)
        || length <= 0
        || length > MAX_PARTSIZE) {
      // XXX - Protocol error -> disconnect?
      if (_log.shouldLog(Log.WARN))
        _log.warn(
            "Got strange 'request: "
                + piece
                + ", "
                + begin
                + ", "
                + length
                + "' message from "
                + peer);
      return;
    }

    // Limit total pipelined requests to MAX_PIPELINE bytes
    // to conserve memory and prevent DOS
    // Todo: limit number of requests also? (robert 64 x 4KB)
    if (out.queuedBytes() + length > MAX_PIPELINE_BYTES) {
      if (_log.shouldLog(Log.WARN))
        _log.warn("Discarding request over pipeline limit from " + peer);
      return;
    }

    if (_log.shouldLog(Log.DEBUG))
      _log.debug("Queueing (" + piece + ", " + begin + ", " + length + ")" + " to " + peer);

    // don't load the data into mem now, let PeerConnectionOut do it
    out.sendPiece(piece, begin, length, this);
  }
Beispiel #7
0
 /** @since 0.8.5 */
 public static void main(String[] args) {
   if (args.length <= 0) {
     System.err.println("Usage: MetaInfo files...");
     return;
   }
   for (int i = 0; i < args.length; i++) {
     InputStream in = null;
     try {
       in = new FileInputStream(args[i]);
       MetaInfo meta = new MetaInfo(in);
       System.out.println(args[i] + " InfoHash: " + I2PSnarkUtil.toHex(meta.getInfoHash()));
     } catch (IOException ioe) {
       System.err.println("Error in file " + args[i] + ": " + ioe);
     } finally {
       try {
         if (in != null) in.close();
       } catch (IOException ioe) {
       }
     }
   }
 }
Beispiel #8
0
  void haveMessage(int piece) {
    if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " rcv have(" + piece + ")");
    // FIXME we will lose these until we get the metainfo
    if (metainfo == null) return;
    // Sanity check
    if (piece < 0 || piece >= metainfo.getPieces()) {
      // XXX disconnect?
      if (_log.shouldLog(Log.WARN))
        _log.warn("Got strange 'have: " + piece + "' message from " + peer);
      return;
    }

    synchronized (this) {
      // Can happen if the other side never send a bitfield message.
      if (bitfield == null) bitfield = new BitField(metainfo.getPieces());

      bitfield.set(piece);
    }

    if (listener.gotHave(peer, piece)) setInteresting(true);
  }
Beispiel #9
0
 /** @since 0.8.2 */
 void extensionMessage(int id, byte[] bs) {
   if (metainfo != null
       && metainfo.isPrivate()
       && (id == ExtensionHandler.ID_METADATA || id == ExtensionHandler.ID_PEX)) {
     // shouldn't get this since we didn't advertise it but they could send it anyway
     if (_log.shouldLog(Log.WARN)) _log.warn("Private torrent, ignoring ext msg " + id);
     return;
   }
   ExtensionHandler.handleMessage(peer, listener, id, bs);
   // Peer coord will get metadata from MagnetState,
   // verify, and then call gotMetaInfo()
   listener.gotExtension(peer, id, bs);
 }
Beispiel #10
0
  public void run() {
    // XXX - Support other IPs
    String announce = meta.getAnnounce();
    String infoHash = urlencode(meta.getInfoHash());
    String peerID = urlencode(coordinator.getID());

    long uploaded = coordinator.getUploaded();
    long downloaded = coordinator.getDownloaded();
    long left = coordinator.getLeft();

    boolean completed = (left == 0);

    try {
      boolean started = false;
      while (!started) {
        try {
          // Send start.
          TrackerInfo info =
              doRequest(announce, infoHash, peerID, uploaded, downloaded, left, STARTED_EVENT);
          Iterator it = info.getPeers().iterator();
          while (it.hasNext()) coordinator.addPeer((Peer) it.next());
          started = true;
        } catch (IOException ioe) {
          // Probably not fatal (if it doesn't last to long...)
          Snark.debug(
              "WARNING: Could not contact tracker at '" + announce + "': " + ioe, Snark.WARNING);
        }

        if (!started && !stop) {
          Snark.debug("         Retrying in one minute...", Snark.DEBUG);
          try {
            // Sleep one minutes...
            Thread.sleep(60 * 1000);
          } catch (InterruptedException interrupt) {
            // ignore
          }
        }
      }

      while (!stop) {
        try {
          // Sleep some minutes...
          Thread.sleep(SLEEP * 60 * 1000);
        } catch (InterruptedException interrupt) {
          // ignore
        }

        if (stop) break;

        uploaded = coordinator.getUploaded();
        downloaded = coordinator.getDownloaded();
        left = coordinator.getLeft();

        // First time we got a complete download?
        String event;
        if (!completed && left == 0) {
          completed = true;
          event = COMPLETED_EVENT;
        } else event = NO_EVENT;

        // Only do a request when necessary.
        if (event == COMPLETED_EVENT
            || coordinator.needPeers()
            || System.currentTimeMillis() > lastRequestTime + interval) {
          try {
            TrackerInfo info =
                doRequest(announce, infoHash, peerID, uploaded, downloaded, left, event);

            Iterator it = info.getPeers().iterator();
            while (it.hasNext()) coordinator.addPeer((Peer) it.next());
          } catch (IOException ioe) {
            // Probably not fatal (if it doesn't last to long...)
            Snark.debug(
                "WARNING: Could not contact tracker at '" + announce + "': " + ioe, Snark.WARNING);
          }
        }
      }
    } catch (Throwable t) {
      Snark.debug("TrackerClient: " + t, Snark.ERROR);
      t.printStackTrace();
    } finally {
      try {
        TrackerInfo info =
            doRequest(announce, infoHash, peerID, uploaded, downloaded, left, STOPPED_EVENT);
      } catch (IOException ioe) {
        /* ignored */
      }
    }
  }
Beispiel #11
0
  /**
   * Adds a new request to the outstanding requests list. Then send interested if we weren't. Then
   * send new requests if not choked. If nothing to request, send not interested if we were.
   *
   * <p>This is called from several places:
   *
   * <pre>
   *   By getOustandingRequest() when the first part of a chunk comes in
   *   By havePiece() when somebody got a new piece completed
   *   By chokeMessage() when we receive an unchoke
   *   By setInteresting() when we are now interested
   *   By PeerCoordinator.updatePiecePriorities()
   * </pre>
   */
  synchronized void addRequest() {
    // no bitfield yet? nothing to request then.
    if (bitfield == null) return;
    if (metainfo == null) return;
    boolean more_pieces = true;
    while (more_pieces) {
      more_pieces = outstandingRequests.size() < MAX_PIPELINE;
      // We want something and we don't have outstanding requests?
      if (more_pieces && lastRequest == null) {
        // we have nothing in the queue right now
        if (!interesting) {
          // If we need something, set interesting but delay pulling
          // a request from the PeerCoordinator until unchoked.
          if (listener.needPiece(this.peer, bitfield)) {
            setInteresting(true);
            if (_log.shouldLog(Log.DEBUG))
              _log.debug(
                  peer
                      + " addRequest() we need something, setting interesting, delaying requestNextPiece()");
          } else {
            if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " addRequest() needs nothing");
          }
          return;
        }
        if (choked) {
          // If choked, delay pulling
          // a request from the PeerCoordinator until unchoked.
          if (_log.shouldLog(Log.DEBUG))
            _log.debug(peer + " addRequest() we are choked, delaying requestNextPiece()");
          return;
        }
        // huh? rv unused
        more_pieces = requestNextPiece();
      } else if (more_pieces) // We want something
      {
        int pieceLength;
        boolean isLastChunk;
        pieceLength = metainfo.getPieceLength(lastRequest.getPiece());
        isLastChunk = lastRequest.off + lastRequest.len == pieceLength;

        // Last part of a piece?
        if (isLastChunk) more_pieces = requestNextPiece();
        else {
          PartialPiece nextPiece = lastRequest.getPartialPiece();
          int nextBegin = lastRequest.off + PARTSIZE;
          int maxLength = pieceLength - nextBegin;
          int nextLength = maxLength > PARTSIZE ? PARTSIZE : maxLength;
          Request req = new Request(nextPiece, nextBegin, nextLength);
          outstandingRequests.add(req);
          if (!choked) out.sendRequest(req);
          lastRequest = req;
        }
      }
    }

    // failsafe
    // However this is bad as it thrashes the peer when we change our mind
    // Ticket 691 cause here?
    if (interesting && lastRequest == null && outstandingRequests.isEmpty()) setInteresting(false);

    if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " requests " + outstandingRequests);
  }
Beispiel #12
0
 /**
  * @return always will be valid even in magnet mode
  * @since 0.8.4
  */
 public byte[] getInfoHash() {
   // should always be the same
   if (meta != null) return meta.getInfoHash();
   return infoHash;
 }
Beispiel #13
0
 /**
  * @return number of pieces
  * @since 0.8.4
  */
 public int getPieces() {
   if (meta != null) return meta.getPieces();
   // FIXME else return metainfo pieces if available
   return -1;
 }
Beispiel #14
0
 /**
  * @param p the piece number
  * @return metainfo piece length or 16K if fetching magnet
  * @since 0.8.4
  */
 public int getPieceLength(int p) {
   if (meta != null) return meta.getPieceLength(p);
   return 16 * 1024;
 }
Beispiel #15
0
 /**
  * @return total of all torrent files, or total of metainfo file if fetching magnet, or -1
  * @since 0.8.4
  */
 public long getTotalLength() {
   if (meta != null) return meta.getTotalLength();
   // FIXME else return metainfo length if available
   return -1;
 }
Beispiel #16
0
  /**
   * Runs the connection to the other peer. This method does not return until the connection is
   * terminated.
   *
   * <p>When the connection is correctly started the connected() method of the given PeerListener is
   * called. If the connection ends or the connection could not be setup correctly the
   * disconnected() method is called.
   *
   * <p>If the given BitField is non-null it is send to the peer as first message.
   */
  public void runConnection(
      I2PSnarkUtil util, PeerListener listener, BitField bitfield, MagnetState mState) {
    if (state != null) throw new IllegalStateException("Peer already started");

    if (_log.shouldLog(Log.DEBUG))
      _log.debug("Running connection to " + peerID.toString(), new Exception("connecting"));
    try {
      // Do we need to handshake?
      if (din == null) {
        // Outgoing connection
        sock = util.connect(peerID);
        if (_log.shouldLog(Log.DEBUG)) _log.debug("Connected to " + peerID + ": " + sock);
        if ((sock == null) || (sock.isClosed())) {
          throw new IOException("Unable to reach " + peerID);
        }
        InputStream in = sock.getInputStream();
        OutputStream out = sock.getOutputStream();
        byte[] id = handshake(in, out);
        byte[] expected_id = peerID.getID();
        if (expected_id == null) {
          peerID.setID(id);
        } else if (Arrays.equals(expected_id, id)) {
          if (_log.shouldLog(Log.DEBUG))
            _log.debug("Handshake got matching IDs with " + toString());
        } else {
          throw new IOException(
              "Unexpected peerID '"
                  + PeerID.idencode(id)
                  + "' expected '"
                  + PeerID.idencode(expected_id)
                  + "'");
        }
      } else {
        // Incoming connection
        if (_log.shouldLog(Log.DEBUG))
          _log.debug("Already have din [" + sock + "] with " + toString());
      }

      // bad idea?
      if (metainfo == null && (options & OPTION_EXTENSION) == 0) {
        if (_log.shouldLog(Log.INFO))
          _log.info("Peer does not support extensions and we need metainfo, dropping");
        throw new IOException("Peer does not support extensions and we need metainfo, dropping");
      }

      PeerConnectionIn in = new PeerConnectionIn(this, din);
      PeerConnectionOut out = new PeerConnectionOut(this, dout);
      PeerState s = new PeerState(this, listener, metainfo, in, out);

      if ((options & OPTION_EXTENSION) != 0) {
        if (_log.shouldLog(Log.DEBUG))
          _log.debug("Peer supports extensions, sending reply message");
        int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
        out.sendExtension(0, ExtensionHandler.getHandshake(metasize));
      }

      if ((options & OPTION_I2P_DHT) != 0 && util.getDHT() != null) {
        if (_log.shouldLog(Log.DEBUG)) _log.debug("Peer supports DHT, sending PORT message");
        int port = util.getDHT().getPort();
        out.sendPort(port);
      }

      // Send our bitmap
      if (bitfield != null) s.out.sendBitfield(bitfield);

      // We are up and running!
      state = s;
      magnetState = mState;
      listener.connected(this);

      if (_log.shouldLog(Log.DEBUG)) _log.debug("Start running the reader with " + toString());
      // Use this thread for running the incomming connection.
      // The outgoing connection creates its own Thread.
      out.startup();
      Thread.currentThread().setName("Snark reader from " + peerID);
      s.in.run();
    } catch (IOException eofe) {
      // Ignore, probably just the other side closing the connection.
      // Or refusing the connection, timing out, etc.
      if (_log.shouldLog(Log.DEBUG)) _log.debug(this.toString(), eofe);
    } catch (Throwable t) {
      _log.error(this + ": " + t.getMessage(), t);
      if (t instanceof OutOfMemoryError) throw (OutOfMemoryError) t;
    } finally {
      if (deregister) listener.disconnected(this);
      disconnect();
    }
  }
Beispiel #17
0
  /**
   * multitorrent
   *
   * @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
   * @throws RuntimeException via fatal()
   * @since 0.9.11
   */
  public Snark(
      I2PSnarkUtil util,
      String torrent,
      String ip,
      int user_port,
      StorageListener slistener,
      CoordinatorListener clistener,
      CompleteListener complistener,
      PeerCoordinatorSet peerCoordinatorSet,
      ConnectionAcceptor connectionAcceptor,
      boolean start,
      String rootDir,
      File baseFile) {
    if (slistener == null) slistener = this;

    completeListener = complistener;
    _util = util;
    _log = util.getContext().logManager().getLog(Snark.class);
    _peerCoordinatorSet = peerCoordinatorSet;
    acceptor = connectionAcceptor;

    this.torrent = torrent;
    this.rootDataDir = new File(rootDir);

    stopped = true;
    activity = "Network setup";

    id = generateID();
    if (_log.shouldLog(Log.INFO)) _log.info("My peer id: " + PeerID.idencode(id));

    /*
     * Don't start a tunnel if the torrent isn't going to be started.
     * If we are starting,
     * startTorrent() will force a connect.
     *
        boolean ok = util.connect();
        if (!ok) fatal("Unable to connect to I2P");
        I2PServerSocket serversocket = util.getServerSocket();
        if (serversocket == null)
            fatal("Unable to listen for I2P connections");
        else {
            Destination d = serversocket.getManager().getSession().getMyDestination();
            debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
        }
    */

    // Figure out what the torrent argument represents.
    File f = null;
    InputStream in = null;
    byte[] x_infoHash = null;
    try {
      f = new File(torrent);
      if (f.exists()) in = new FileInputStream(f);
      else {
        /**
         * ** No, we don't ever fetch a torrent file this way and we don't want to block in the
         * constructor activity = "Getting torrent"; File torrentFile = _util.get(torrent, 3); if
         * (torrentFile == null) { fatal("Unable to fetch " + torrent); if (false) return; // never
         * reached - fatal(..) throws } else { torrentFile.deleteOnExit(); in = new
         * FileInputStream(torrentFile); } ***
         */
        throw new IOException("not found");
      }
      meta = new MetaInfo(in);
      x_infoHash = meta.getInfoHash();
    } catch (IOException ioe) {
      // OK, so it wasn't a torrent metainfo file.
      if (f != null && f.exists())
        if (ip == null)
          fatal(
              "'"
                  + torrent
                  + "' exists,"
                  + " but is not a valid torrent metainfo file."
                  + System.getProperty("line.separator"),
              ioe);
        else fatal("I2PSnark does not support creating and tracking a torrent at the moment");
      /*
         {
           // Try to create a new metainfo file
          debug
            ("Trying to create metainfo torrent for '" + torrent + "'",
             NOTICE);
          try
            {
              activity = "Creating torrent";
              storage = new Storage
                (f, "http://" + ip + ":" + port + "/announce", slistener);
              storage.create();
              meta = storage.getMetaInfo();
            }
          catch (IOException ioe2)
            {
              fatal("Could not create torrent for '" + torrent + "'", ioe2);
            }
         }
      */
      else fatal("Cannot open '" + torrent + "'", ioe);
    } catch (OutOfMemoryError oom) {
      fatal("ERROR - Out of memory, cannot create torrent " + torrent + ": " + oom.getMessage());
    } finally {
      if (in != null)
        try {
          in.close();
        } catch (IOException ioe) {
        }
    }

    infoHash = x_infoHash; // final
    if (_log.shouldLog(Log.INFO)) _log.info(meta.toString());

    // When the metainfo torrent was created from an existing file/dir
    // it already exists.
    if (storage == null) {
      try {
        activity = "Checking storage";
        boolean shouldPreserve =
            completeListener != null && completeListener.getSavedPreserveNamesSetting(this);
        if (baseFile == null) {
          String base = meta.getName();
          if (!shouldPreserve) base = Storage.filterName(base);
          if (_util.getFilesPublic()) baseFile = new File(rootDataDir, base);
          else baseFile = new SecureFile(rootDataDir, base);
        }
        storage = new Storage(_util, baseFile, meta, slistener, shouldPreserve);
        if (completeListener != null) {
          storage.check(
              completeListener.getSavedTorrentTime(this),
              completeListener.getSavedTorrentBitField(this));
        } else {
          storage.check();
        }
        // have to figure out when to reopen
        // if (!start)
        //    storage.close();
      } catch (IOException ioe) {
        try {
          storage.close();
        } catch (IOException ioee) {
          ioee.printStackTrace();
        }
        fatal("Could not check or create storage", ioe);
      }
    }

    /*
     * see comment above
     *
        activity = "Collecting pieces";
        coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
        PeerCoordinatorSet set = PeerCoordinatorSet.instance();
        set.add(coordinator);
        ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
        acceptor.startAccepting(set, serversocket);
        trackerclient = new TrackerClient(meta, coordinator);
    */

    savedUploaded = (completeListener != null) ? completeListener.getSavedUploaded(this) : 0;
    if (start) startTorrent();
  }