Пример #1
0
  /**
   * Called when a full chunk (i.e. a piece message) has been received by PeerConnectionIn.
   *
   * <p>This may block quite a while if it is the last chunk for a piece, as it calls the listener,
   * who stores the piece and then calls havePiece for every peer on the torrent (including us).
   */
  void pieceMessage(Request req) {
    int size = req.len;
    peer.downloaded(size);
    listener.downloaded(peer, size);

    if (_log.shouldLog(Log.DEBUG))
      _log.debug(
          "got end of Chunk(" + req.getPiece() + "," + req.off + "," + req.len + ") from " + peer);

    // Last chunk needed for this piece?
    // FIXME if priority changed to skip, we will think we're done when we aren't
    if (getFirstOutstandingRequest(req.getPiece()) == -1) {
      // warning - may block here for a while
      if (listener.gotPiece(peer, req.getPartialPiece())) {
        if (_log.shouldLog(Log.DEBUG)) _log.debug("Got " + req.getPiece() + ": " + peer);
      } else {
        if (_log.shouldLog(Log.WARN)) _log.warn("Got BAD " + req.getPiece() + " from " + peer);
      }
    }

    // ok done with this one
    synchronized (this) {
      pendingRequest = null;
    }
  }
Пример #2
0
 /**
  * Can't find a published standard for this anywhere. See the libtorrent code. Here we use the
  * "added" key as a single string of concatenated 32-byte peer hashes. added.f and dropped
  * unsupported
  *
  * @since 0.8.4
  */
 private static void handlePEX(Peer peer, PeerListener listener, byte[] bs, Log log) {
   if (log.shouldLog(Log.DEBUG)) log.debug("Got PEX msg from " + peer);
   try {
     InputStream is = new ByteArrayInputStream(bs);
     BDecoder dec = new BDecoder(is);
     BEValue bev = dec.bdecodeMap();
     Map<String, BEValue> map = bev.getMap();
     bev = map.get("added");
     if (bev == null) return;
     byte[] ids = bev.getBytes();
     if (ids.length < HASH_LENGTH) return;
     int len = Math.min(ids.length, (I2PSnarkUtil.MAX_CONNECTIONS - 1) * HASH_LENGTH);
     List<PeerID> peers = new ArrayList<PeerID>(len / HASH_LENGTH);
     for (int off = 0; off < len; off += HASH_LENGTH) {
       byte[] hash = new byte[HASH_LENGTH];
       System.arraycopy(ids, off, hash, 0, HASH_LENGTH);
       if (DataHelper.eq(hash, peer.getPeerID().getDestHash())) continue;
       PeerID pID = new PeerID(hash, listener.getUtil());
       peers.add(pID);
     }
     // could include ourselves, listener must remove
     listener.gotPeers(peer, peers);
   } catch (Exception e) {
     if (log.shouldLog(Log.INFO)) log.info("PEX msg exception from " + peer, e);
     // peer.disconnect(false);
   }
 }
Пример #3
0
  void disconnect() {
    PeerState s = state;
    if (s != null) {
      // try to save partial piece
      if (this.deregister) {
        PeerListener p = s.listener;
        if (p != null) {
          List<PartialPiece> pcs = s.returnPartialPieces();
          if (!pcs.isEmpty()) p.savePartialPieces(this, pcs);
          // now covered by savePartialPieces
          // p.markUnrequested(this);
        }
      }
      state = null;

      PeerConnectionIn in = s.in;
      if (in != null) in.disconnect();
      PeerConnectionOut out = s.out;
      if (out != null) out.disconnect();
      PeerListener pl = s.listener;
      if (pl != null) pl.disconnected(this);
    }
    I2PSocket csock = sock;
    sock = null;
    if ((csock != null) && (!csock.isClosed())) {
      try {
        csock.close();
      } catch (IOException ioe) {
        _log.warn("Error disconnecting " + toString(), ioe);
      }
    }
  }
Пример #4
0
  /**
   * This is the callback that PeerConnectionOut calls
   *
   * @return bytes or null for errors
   * @since 0.8.2
   */
  public ByteArray loadData(int piece, int begin, int length) {
    ByteArray pieceBytes = listener.gotRequest(peer, piece, begin, length);
    if (pieceBytes == null) {
      // XXX - Protocol error-> diconnect?
      if (_log.shouldLog(Log.WARN)) _log.warn("Got request for unknown piece: " + piece);
      return null;
    }

    // More sanity checks
    if (length != pieceBytes.getData().length) {
      // XXX - Protocol error-> disconnect?
      if (_log.shouldLog(Log.WARN))
        _log.warn(
            "Got out of range 'request: "
                + piece
                + ", "
                + begin
                + ", "
                + length
                + "' message from "
                + peer);
      return null;
    }

    if (_log.shouldLog(Log.DEBUG))
      _log.debug("Sending (" + piece + ", " + begin + ", " + length + ")" + " to " + peer);
    return pieceBytes;
  }
Пример #5
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);
    }
  }
Пример #6
0
  /**
   * Starts requesting first chunk of next piece. Returns true if something has been added to the
   * requests, false otherwise. Caller should synchronize.
   */
  private boolean requestNextPiece() {
    // Check that we already know what the other side has.
    if (bitfield != null) {
      // Check for adopting an orphaned partial piece
      PartialPiece pp = listener.getPartialPiece(peer, bitfield);
      if (pp != null) {
        // Double-check that r not already in outstandingRequests
        if (!getRequestedPieces().contains(Integer.valueOf(pp.getPiece()))) {
          Request r = pp.getRequest();
          outstandingRequests.add(r);
          if (!choked) out.sendRequest(r);
          lastRequest = r;
          return true;
        } else {
          if (_log.shouldLog(Log.WARN)) _log.warn("Got dup from coord: " + pp);
          pp.release();
        }
      }

      /**
       * ***** getPartialPiece() does it all now // Note that in addition to the bitfield,
       * PeerCoordinator uses // its request tracking and isRequesting() to determine // what piece
       * to give us next. int nextPiece = listener.wantPiece(peer, bitfield); if (nextPiece != -1 &&
       * (lastRequest == null || lastRequest.getPiece() != nextPiece)) { if
       * (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " want piece " + nextPiece); // Fail safe to
       * make sure we are interested // When we transition into the end game we may not be
       * interested... if (!interesting) { if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + "
       * transition to end game, setting interesting"); interesting = true; out.sendInterest(true);
       * }
       *
       * <p>int piece_length = metainfo.getPieceLength(nextPiece); //Catch a common place for OOMs
       * esp. on 1MB pieces byte[] bs; try { bs = new byte[piece_length]; } catch (OutOfMemoryError
       * oom) { _log.warn("Out of memory, can't request piece " + nextPiece, oom); return false; }
       *
       * <p>int length = Math.min(piece_length, PARTSIZE); Request req = new Request(nextPiece, bs,
       * 0, length); outstandingRequests.add(req); if (!choked) out.sendRequest(req); lastRequest =
       * req; return true; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " no more
       * pieces to request"); } *****
       */
    }

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

    // If we are not in the end game, we may run out of things to request
    // because we are asking other peers. Set not-interesting now rather than
    // wait for those other requests to be satisfied via havePiece()
    if (interesting && lastRequest == null) {
      interesting = false;
      out.sendInterest(false);
      if (_log.shouldLog(Log.DEBUG))
        _log.debug(peer + " nothing more to request, now uninteresting");
    }
    return false;
  }
Пример #7
0
  // Called when someone who's just connected to us sends a messages
  private void processNewSocket(SocketChannel c) throws IOException {
    int id = ChannelHelper.getInt(c);
    int port = ChannelHelper.getInt(c);
    String name = ChannelHelper.getString(c);

    // Move to connected peers lists
    Peer peer = new Peer(id, name, c.socket().getInetAddress(), port);
    new_sockets.remove(c);
    peers.put(c, peer);
    sockets.put(peer, c);

    if (listener != null) listener.addPeer(peer);
  }
Пример #8
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);
 }
Пример #9
0
  void chokeMessage(boolean choke) {
    if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");

    boolean resend = choked && !choke;
    choked = choke;

    listener.gotChoke(peer, choke);

    if (interesting && !choked) request(resend);

    if (choked) {
      out.cancelRequestMessages();
      // old Roberts thrash us here, choke+unchoke right together
      // The only problem with returning the partials to the coordinator
      // is that chunks above a missing request are lost.
      // Future enhancements to PartialPiece could keep track of the holes.
      List<Request> pcs = returnPartialPieces();
      if (!pcs.isEmpty()) {
        if (_log.shouldLog(Log.DEBUG))
          _log.debug(peer + " got choked, returning partial pieces to the PeerCoordinator: " + pcs);
        listener.savePartialPieces(this.peer, pcs);
      }
    }
  }
Пример #10
0
 /**
  * Receive the DHT port numbers
  *
  * @since DHT
  */
 private static void handleDHT(Peer peer, PeerListener listener, byte[] bs, Log log) {
   if (log.shouldLog(Log.DEBUG)) log.debug("Got DHT msg from " + peer);
   try {
     InputStream is = new ByteArrayInputStream(bs);
     BDecoder dec = new BDecoder(is);
     BEValue bev = dec.bdecodeMap();
     Map<String, BEValue> map = bev.getMap();
     int qport = map.get("port").getInt();
     int rport = map.get("rport").getInt();
     listener.gotPort(peer, qport, rport);
   } catch (Exception e) {
     if (log.shouldLog(Log.INFO)) log.info("DHT msg exception from " + peer, e);
     // peer.disconnect(false);
   }
 }
Пример #11
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);
  }
Пример #12
0
  // Called when an established peer sends us a message
  private void processPeerMessage(SocketChannel c) throws IOException {
    Peer peer = peers.get(c);

    ByteBuffer b = ByteBuffer.allocateDirect(1);
    int read = c.read(b);
    if (read < 0) {
      c.close();
      return;
    }
    if (read < 1) return; // No data?
    b.flip();
    byte type = b.get();

    switch (type) {
      case MSG_DEBUG:
        log(peer.getName() + ": " + ChannelHelper.getString(c));
        break;
      case MSG_BALL:
        int id = ChannelHelper.getInt(c);
        double position = ChannelHelper.getDouble(c);
        double dx = ChannelHelper.getDouble(c);
        double dy = ChannelHelper.getDouble(c);
        int D = ChannelHelper.getInt(c);
        if (listener != null) listener.receiveBall(id, position, dx, dy, D);
        break;
        //		Dropped ball
        //			Informative to all peers & server
        //			(Maybe implement as passing ball to server?)
        //			Server may respond with “you lose”
        //		Drop player
        //			Paxos confirmation that client is gone
        //		Heartbeat?
        //			Probably only sent in response to drop
        //			Ping/pong messages
        //		Server failure
        //			All peers, send choice of backup
      default:
        System.err.println("Unknown peer message: " + type);
    }
  }
Пример #13
0
 /**
  * Called when some bytes have left the outgoing connection. XXX - Should indicate whether it was
  * a real piece or overhead.
  */
 void uploaded(int size) {
   peer.uploaded(size);
   listener.uploaded(peer, size);
 }
Пример #14
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();
    }
  }
Пример #15
0
  /**
   * REF: BEP 9
   *
   * @since 0.8.4
   */
  private static void handleMetadata(Peer peer, PeerListener listener, byte[] bs, Log log) {
    if (log.shouldLog(Log.DEBUG)) log.debug("Got metadata msg from " + peer);
    try {
      InputStream is = new ByteArrayInputStream(bs);
      BDecoder dec = new BDecoder(is);
      BEValue bev = dec.bdecodeMap();
      Map<String, BEValue> map = bev.getMap();
      int type = map.get("msg_type").getInt();
      int piece = map.get("piece").getInt();

      MagnetState state = peer.getMagnetState();
      if (type == TYPE_REQUEST) {
        if (log.shouldLog(Log.DEBUG)) log.debug("Got request for " + piece + " from: " + peer);
        byte[] pc;
        synchronized (state) {
          pc = state.getChunk(piece);
        }
        sendPiece(peer, piece, pc);
        // Do this here because PeerConnectionOut only reports for PIECE messages
        peer.uploaded(pc.length);
        listener.uploaded(peer, pc.length);
      } else if (type == TYPE_DATA) {
        int size = map.get("total_size").getInt();
        if (log.shouldLog(Log.DEBUG))
          log.debug("Got data for " + piece + " length " + size + " from: " + peer);
        boolean done;
        int chk = -1;
        synchronized (state) {
          if (state.isComplete()) return;
          int len = is.available();
          if (len != size) {
            // probably fatal
            if (log.shouldLog(Log.WARN)) log.warn("total_size " + size + " but avail data " + len);
          }
          peer.downloaded(len);
          listener.downloaded(peer, len);
          done = state.saveChunk(piece, bs, bs.length - len, len);
          if (log.shouldLog(Log.INFO)) log.info("Got chunk " + piece + " from " + peer);
          if (!done) chk = state.getNextRequest();
        }
        // out of the lock
        if (done) {
          // Done!
          // PeerState will call the listener (peer coord), who will
          // check to see if the MagnetState has it
          if (log.shouldLog(Log.WARN)) log.warn("Got last chunk from " + peer);
        } else {
          // get the next chunk
          if (log.shouldLog(Log.INFO)) log.info("Request chunk " + chk + " from " + peer);
          sendRequest(peer, chk);
        }
      } else if (type == TYPE_REJECT) {
        if (log.shouldLog(Log.WARN)) log.warn("Got reject msg from " + peer);
        peer.disconnect(false);
      } else {
        if (log.shouldLog(Log.WARN)) log.warn("Got unknown metadata msg from " + peer);
        peer.disconnect(false);
      }
    } catch (Exception e) {
      if (log.shouldLog(Log.INFO)) log.info("Metadata ext. msg. exception from " + peer, e);
      // fatal ?
      peer.disconnect(false);
    }
  }
Пример #16
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);
  }
Пример #17
0
 /**
  * Unused
  *
  * @since 0.8.4
  */
 void portMessage(int port) {
   // for compatibility with old DHT PORT message
   listener.gotPort(peer, port, port + 1);
 }
Пример #18
0
 void interestedMessage(boolean interest) {
   if (_log.shouldLog(Log.DEBUG))
     _log.debug(peer + " rcv " + (interest ? "" : "un") + "interested");
   interested = interest;
   listener.gotInterest(peer, interest);
 }