/** * 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; } }
/** * 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); } }
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); } } }
/** * 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; }
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); } }
/** * 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; }
// 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); }
/** @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); }
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); } } }
/** * 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); } }
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); }
// 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); } }
/** * 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); }
/** * 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(); } }
/** * 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); } }
/** * 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); }
/** * Unused * * @since 0.8.4 */ void portMessage(int port) { // for compatibility with old DHT PORT message listener.gotPort(peer, port, port + 1); }
void interestedMessage(boolean interest) { if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " rcv " + (interest ? "" : "un") + "interested"); interested = interest; listener.gotInterest(peer, interest); }