java.lang.Object
org.klomp.snark.PeerState
All Implemented Interfaces:
DataLoader

class PeerStateextends Object implements DataLoader
Manages the state and protocol logic for a BitTorrent peer connection.
This class tracks peer interest/choking status, piece availability, request queues, and handles all BitTorrent protocol message processing. Methods that inspect or change the state synchronize (on this).
  • Field Summary

    Modifier and Type
    Field
    Description
    private Set<Integer>
    BEP 6: pieces we advertised to the peer, servable even while choking it.
    private long
    Bytes of piece data the peer cancelled after requesting.
    (package private) Set<Integer>
    BEP 6: pieces a conforming sender may advertise to us.
    private long
    Time of the last inbound request from the peer.
    private final Log
    Logger for this class.
    (package private) final Set<Integer>
    BEP 6: pieces the peer advertised to us, requestable while it chokes us.
    (package private) BitField
    The pieces the peer has.
    private final BandwidthListener
    Told about bytes transferred with this peer.
    (package private) boolean
    Whether the peer has choked us.
    (package private) boolean
    Whether we are choking the peer.
    private int
    Most requests that may be outstanding at once.
    private static final int
    Default cap on a single piece request from a peer.
    private static final int
    Default maximum request pipeline depth for outbound requests.
    private static final int
    Default minimum request pipeline depth for outbound requests.
    private static final int
    Default chunk size for outbound piece requests.
    private static final int
    Discard-ratio auto-ban: cancelled percentage above which the peer is banned.
    private static final long
    Discard-ratio auto-ban: minimum cancelled+uploaded bytes before evaluation.
    private List<Integer>
    Null unless needed.
    (package private) final PeerConnectionIn
    Inbound connection.
    (package private) boolean
    Whether the peer is interested in us.
    (package private) boolean
    Whether we are interested in the peer.
    private long
    Last time a choke message was sent.
    private Request
    The tail (NOT the head) of the request queue.
    (package private) final PeerListener
    Fixme, used by Peer.disconnect() to get to the coordinator.
    private static final byte[]
    The four zero bytes libtorrent uses in place of an IPv4 address when its remote peer is an I2P destination; shared read-only.
    static int
    Cap on a single piece request from a peer.
    static int
    Maximum request pipeline depth for outbound requests.
    private static final int
    Absolute cap on pipeline depth, outbound or inbound.
    private MetaInfo
    Null before we have it.
    static int
    Minimum request pipeline depth for outbound requests.
    (package private) final PeerConnectionOut
    Outbound connection.
    private final List<Request>
    Piece requests sent but not yet answered.
    static int
    Chunk size for outbound piece requests.
    private final Peer
    The peer this state describes.
    private Request
    Flag to back up from firstOutstandingRequest when calculating progress.
    private static final Integer
    Sentinel meaning every piece rather than one.
    private static final long
    Anti-snub: unchoked peers that send no request for this long are snubbing.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Create the peer state for the torrent.
  • Method Summary

    Modifier and Type
    Method
    Description
    (package private) void
    Adds a new request to the outstanding requests list.
    (package private) void
    allowedFastMessage(int piece)
    Handle an allowed fast message (BEP 6): the peer will serve this piece even while it chokes us.
    (package private) void
    bitfieldMessage(byte[] bitmap)
    Handle a bitfield message from the peer.
    private void
    bitfieldMessage(byte[] bitmap, boolean isAll)
    Mark the peer's bitfield.
    (package private) void
    cancelMessage(int piece, int begin, int length)
    Handle a cancel message from the peer.
    (package private) void
    cancelPiece(int piece)
    Tell the other side that we are no longer interested in any of the outstanding requests (if any) for this piece.
    private void
    checkDiscardRatio(int length)
    Count cancelled bytes and ban the peer if it cancels most of what it requests.
    (package private) void
    chokeMessage(boolean choke)
    Handle a choke or unchoke message from the peer.
    (package private) void
    dontHaveMessage(int piece)
    Handle a BEP 54 lt_donthave message: the peer no longer has a piece it advertised.
    (package private) Set<Integer>
    The set of piece indices a conforming sender may advertise to us, or null when nothing can be derived.
    (package private) void
    extensionMessage(int id, byte[] bs)
    Handle an extension message from the peer.
    (package private) static Set<Integer>
    generateAllowedFastSet(byte[] peerHash, byte[] infohash, int pieces)
    Generate the BEP 6 allowed fast set for a peer.
    (package private) BandwidthListener
    Returns the bandwidth listener for this peer state.
    private int
    Finds the first outstanding request for a piece.
    private Request
    Finds the outstanding request for the piece with the lowest offset.
    (package private) Request
    getOutstandingRequest(int piece, int begin, int length)
    Called when a piece message is being processed by the incoming connection.
    private int
    Effective request pipeline depth for this peer.
    private Set<Integer>
    All pieces we are currently requesting, or an empty set.
    (package private) String
    Debug string for the peer, or null.
    (package private) void
    haveMessage(boolean isAll)
    BEP 6.
    (package private) void
    haveMessage(int piece)
    Handle a have message from the peer.
    (package private) void
    havePiece(int piece)
    We now have this piece.
    (package private) void
    interestedMessage(boolean interest)
    Handle an interested or uninterested message from the peer.
    (package private) boolean
    isAllowedFast(int piece)
    Whether the piece is in our BEP 6 allowed fast set for this peer.
    (package private) boolean
    True if the peer is snubbing us: it is unchoked by us and interested but has sent no request for SNUB_TIMEOUT, and nothing is queued to send it.
    (package private) void
    Send a keepalive message to the peer.
    (package private) void
    Handle a keepalive message from the peer.
    loadData(int piece, int begin, int length)
    This is the callback that PeerConnectionOut calls.
    static int
    Maximum bytes of pipelined inbound requests to a single peer.
    (package private) void
    Called when a full chunk (i.e.
    (package private) void
    portMessage(int port)
    Handle a port message from the peer.
    (package private) void
    rejectMessage(int piece, int begin, int length)
    BEP 6.
    private void
    request(boolean resend)
    Starts or resumes requesting pieces.
    (package private) void
    requestMessage(int piece, int begin, int length)
    Handle a request message from the peer.
    private boolean
    Starts requesting first chunk of next piece.
    (package private) void
    Retransmit outstanding requests if necessary.
    (package private) List<Request>
    Return partial pieces to the PeerCoordinator and clear the request queue.
    (package private) List<Request>
    Return the partial piece for a piece to the PeerCoordinator.
    (package private) void
    Generate and queue the BEP 6 allowed fast set for this peer.
    (package private) void
    setChoking(boolean choke)
    Whether we are choking the peer.
    (package private) void
    setInteresting(boolean interest)
    Whether we are interested in pieces from this peer.
    void
    Switch from magnet mode to normal mode.
    static void
    setPipelineParams(int min, int max, int chunk, int maxPart)
    Apply configurable pipeline bounds, chunk size, and per-request cap, clamped to safe limits.
    (package private) void
    suggestMessage(int piece)
    BEP 6 Suggest.
    (package private) void
    unknownMessage(int type, byte[] bs)
    Handle an unknown message type from the peer.
    (package private) void
    uploaded(int size)
    Called when some bytes have left the outgoing connection.

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • _log

      private final Log _log
      Logger for this class.
    • peer

      private final Peer peer
      The peer this state describes.
    • listener

      final PeerListener listener
      Fixme, used by Peer.disconnect() to get to the coordinator.
    • bwListener

      private final BandwidthListener bwListener
      Told about bytes transferred with this peer.
    • metainfo

      private MetaInfo metainfo
      Null before we have it. locking: this.
    • havesBeforeMetaInfo

      private List<Integer> havesBeforeMetaInfo
      Null unless needed. Contains -1 for all. locking: this.
    • interesting

      volatile boolean interesting
      Whether we are interested in the peer.
    • choking

      volatile boolean choking
      Whether we are choking the peer.
    • interested

      volatile boolean interested
      Whether the peer is interested in us.
    • choked

      volatile boolean choked
      Whether the peer has choked us.
    • _allowedFast

      private volatile Set<Integer> _allowedFast
      BEP 6: pieces we advertised to the peer, servable even while choking it.
    • _peerAllowedFast

      final Set<Integer> _peerAllowedFast
      BEP 6: pieces the peer advertised to us, requestable while it chokes us.
    • _expectedAllowedFast

      Set<Integer> _expectedAllowedFast
      BEP 6: pieces a conforming sender may advertise to us. The union of two derivations: our own destination-hash-based set (I2PSnark peers) and the libtorrent-style set derived from a zeroed IPv4 address (qBittorrent and other libtorrent clients over I2P, whose "remote address" is 0.0.0.0 for every I2P connection). Null until computed, or when nothing can be derived.
    • LT_I2P_ADDR_PREFIX

      private static final byte[] LT_I2P_ADDR_PREFIX
      The four zero bytes libtorrent uses in place of an IPv4 address when its remote peer is an I2P destination; shared read-only.
    • bitfield

      BitField bitfield
      The pieces the peer has. Locking: this.
    • in

      final PeerConnectionIn in
      Inbound connection.
    • out

      final PeerConnectionOut out
      Outbound connection.
    • outstandingRequests

      private final List<Request> outstandingRequests
      Piece requests sent but not yet answered.
    • _cancelledBytes

      private long _cancelledBytes
      Bytes of piece data the peer cancelled after requesting.
    • _lastRequestTime

      private long _lastRequestTime
      Time of the last inbound request from the peer.
    • SNUB_TIMEOUT

      private static final long SNUB_TIMEOUT
      Anti-snub: unchoked peers that send no request for this long are snubbing. Measured from the last request, or from the last unchoke we sent.
      See Also:
    • lastRequest

      private Request lastRequest
      The tail (NOT the head) of the request queue.
    • currentMaxPipeline

      private int currentMaxPipeline
      Most requests that may be outstanding at once.
    • DEFAULT_MIN_PIPELINE

      private static final int DEFAULT_MIN_PIPELINE
      Default minimum request pipeline depth for outbound requests.
      See Also:
    • DEFAULT_MAX_PIPELINE

      private static final int DEFAULT_MAX_PIPELINE
      Default maximum request pipeline depth for outbound requests.
      See Also:
    • DEFAULT_PARTSIZE

      private static final int DEFAULT_PARTSIZE
      Default chunk size for outbound piece requests.
      See Also:
    • DEFAULT_MAX_PARTSIZE

      private static final int DEFAULT_MAX_PARTSIZE
      Default cap on a single piece request from a peer.
      See Also:
    • DISCARD_MIN_BYTES

      private static final long DISCARD_MIN_BYTES
      Discard-ratio auto-ban: minimum cancelled+uploaded bytes before evaluation.
      See Also:
    • DISCARD_BAN_PERCENT

      private static final int DISCARD_BAN_PERCENT
      Discard-ratio auto-ban: cancelled percentage above which the peer is banned.
      See Also:
    • MAX_PIPELINE_CAP

      private static final int MAX_PIPELINE_CAP
      Absolute cap on pipeline depth, outbound or inbound.
      See Also:
    • MIN_PIPELINE

      public static volatile int MIN_PIPELINE
      Minimum request pipeline depth for outbound requests.
    • MAX_PIPELINE

      public static volatile int MAX_PIPELINE
      Maximum request pipeline depth for outbound requests. Bumped from 16 to 32 so a single peer can keep enough chunks in flight to saturate a fast connection at tunnel latency; the per-connection AIMD still dials this down under bandwidth pressure.
    • PARTSIZE

      public static volatile int PARTSIZE
      Chunk size for outbound piece requests.
    • MAX_PARTSIZE

      public static volatile int MAX_PARTSIZE
      Cap on a single piece request from a peer.
    • PIECE_ALL

      private static final Integer PIECE_ALL
      Sentinel meaning every piece rather than one.
    • pendingRequest

      private Request pendingRequest
      Flag to back up from firstOutstandingRequest when calculating progress.
    • lastChokeSendTime

      private long lastChokeSendTime
      Last time a choke message was sent.
  • Constructor Details

    • PeerState

      PeerState(Peer peer, PeerListener listener, BandwidthListener bwl, MetaInfo metainfo, PeerConnectionIn in, PeerConnectionOut out)
      Create the peer state for the torrent.
      Parameters:
      peer - the peer this state tracks, whose availability and stats drive the decisions
      listener - the coordinator callback told about choke, interest, haves and requests
      bwl - the bandwidth listener notified of the bytes uploaded to the peer
      metainfo - null if in magnet mode
      in - the connection the peer's messages arrive on
      out - the connection requests and control messages are sent on
  • Method Details

    • maxPipelineBytes

      public static int maxPipelineBytes()
      Maximum bytes of pipelined inbound requests to a single peer. Derived from MAX_PIPELINE and PARTSIZE, so it follows the configurable values.
      Returns:
      the inbound pipeline byte ceiling
    • setPipelineParams

      public static void setPipelineParams(int min, int max, int chunk, int maxPart)
      Apply configurable pipeline bounds, chunk size, and per-request cap, clamped to safe limits. Call once at startup; standalone snark reads these from its own config file.
      Parameters:
      min - minimum pipeline depth, clamped to [1, max]
      max - maximum pipeline depth, clamped to [min, MAX_PIPELINE_CAP]
      chunk - request chunk size in bytes, clamped to [1KiB, maxPart]
      maxPart - cap on a single request in bytes, clamped to [chunk, 1MiB]
    • getPipelineLimit

      private int getPipelineLimit()
      Effective request pipeline depth for this peer. When the torrent's piece length is smaller than a chunk size, each request carries fewer bytes, so we scale the count up to keep roughly the same volume in flight. Starts from the negotiated peer reqq and is capped at the absolute pipeline limit, keeping the byte volume intact for small pieces.
      Returns:
      the pipeline limit for the current torrent, at least MIN_PIPELINE
    • getBandwidthListener

      BandwidthListener getBandwidthListener()
      Returns the bandwidth listener for this peer state.
      Returns:
      the bandwidth listener
    • keepAliveMessage

      void keepAliveMessage()
      Handle a keepalive message from the peer.
    • chokeMessage

      void chokeMessage(boolean choke)
      Handle a choke or unchoke message from the peer.
      Parameters:
      choke - true if the peer is now choking us, false if it has unchoked
    • interestedMessage

      void interestedMessage(boolean interest)
      Handle an interested or uninterested message from the peer.
      Parameters:
      interest - true if the peer is now interested in us, false if not
    • haveMessage

      void haveMessage(int piece)
      Handle a have message from the peer.
      Parameters:
      piece - the zero-based index of the piece being acted on
    • bitfieldMessage

      void bitfieldMessage(byte[] bitmap)
      Handle a bitfield message from the peer.
      Parameters:
      bitmap - the peer's have bits as received, highest bit first
    • bitfieldMessage

      private void bitfieldMessage(byte[] bitmap, boolean isAll)
      Mark the peer's bitfield.
      Parameters:
      bitmap - null to use the isAll param
      isAll - only if bitmap == null: true for have_all, false for have_none
    • requestMessage

      void requestMessage(int piece, int begin, int length)
      Handle a request message from the peer.
      Parameters:
      piece - the zero-based index of the piece being acted on
      begin - the byte offset into the piece where the requested block starts
      length - the size of the requested block in bytes
    • loadData

      public ByteArray loadData(int piece, int begin, int length)
      This is the callback that PeerConnectionOut calls.
      Specified by:
      loadData in interface DataLoader
      Parameters:
      piece - index of the requested piece
      begin - byte offset within the piece where the request starts
      length - number of bytes requested
      Returns:
      bytes or null for errors such as not having the piece yet
      Throws:
      RuntimeException - on IOE getting the data
    • uploaded

      void uploaded(int size)
      Called when some bytes have left the outgoing connection. Does not indicate whether the bytes were a real piece or overhead.
      Parameters:
      size - the number of bytes just sent to the peer
    • isSnubbing

      boolean isSnubbing()
      True if the peer is snubbing us: it is unchoked by us and interested but has sent no request for SNUB_TIMEOUT, and nothing is queued to send it. The clock restarts when we unchoke the peer, so a freshly unchoked peer always gets the full window to respond.
      Returns:
      true if the peer is snubbing
    • pieceMessage

      void pieceMessage(Request req)
      Called when a full chunk (i.e. a piece message) has been received by PeerConnectionIn.

      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).

      Parameters:
      req - the request satisfied by the block that just arrived
    • getFirstOutstandingRequest

      private int getFirstOutstandingRequest(int piece)
      Finds the first outstanding request for a piece.
      Parameters:
      piece - the piece number asked about, or PIECE_ALL
      Returns:
      its position, -1 if there is none
    • getOutstandingRequest

      Request getOutstandingRequest(int piece, int begin, int length)
      Called when a piece message is being processed by the incoming connection. That is, when the header of the piece message was received. Returns null when there was no such request. It also requeues/sends requests when it thinks that they must have been lost.
      Parameters:
      piece - the zero-based index of the piece being acted on
      begin - the byte offset into the piece where the received block starts
      length - the size of the received block in bytes
      Returns:
      the outstanding request matching that chunk, or null if none was sent
    • getLowestOutstandingRequest

      private Request getLowestOutstandingRequest(int piece)
      Finds the outstanding request for the piece with the lowest offset.
      Parameters:
      piece - the piece number to look for among the outstanding requests
      Returns:
      the request, null if none outstanding for the piece
    • returnPartialPieces

      List<Request> returnPartialPieces()
      Return partial pieces to the PeerCoordinator and clear the request queue.
      Returns:
      List of PartialPieces, even those with an offset == 0, or empty list
    • returnPartialPieces

      List<Request> returnPartialPieces(int piece)
      Return the partial piece for a piece to the PeerCoordinator. Clears the requests for that piece.
      Parameters:
      piece - the piece index
      Returns:
      List of one PartialPiece, or empty list
    • getRequestedPieces

      private Set<Integer> getRequestedPieces()
      All pieces we are currently requesting, or an empty set.
      Returns:
      all pieces we are currently requesting, or empty Set
    • cancelMessage

      void cancelMessage(int piece, int begin, int length)
      Handle a cancel message from the peer.
      Parameters:
      piece - the zero-based index of the piece being acted on
      begin - the byte offset into the piece where the cancelled block starts
      length - the size of the cancelled block in bytes
    • checkDiscardRatio

      private void checkDiscardRatio(int length)
      Count cancelled bytes and ban the peer if it cancels most of what it requests. Conservative: requires at least DISCARD_MIN_BYTES of volume and a cancel ratio above DISCARD_BAN_PERCENT, so legitimate peers under congestion are not affected.
      Parameters:
      length - the number of cancelled bytes
    • extensionMessage

      void extensionMessage(int id, byte[] bs)
      Handle an extension message from the peer.
      Parameters:
      id - the extension message ID
      bs - the message payload
    • dontHaveMessage

      void dontHaveMessage(int piece)
      Handle a BEP 54 lt_donthave message: the peer no longer has a piece it advertised. Without the Fast extension, silently cancel outstanding requests for the piece.
      Parameters:
      piece - the piece index
    • setMetaInfo

      public void setMetaInfo(MetaInfo meta)
      Switch from magnet mode to normal mode. If we already have the metainfo, this does nothing.
      Parameters:
      meta - non-null
    • portMessage

      void portMessage(int port)
      Handle a port message from the peer.
      Parameters:
      port - the port number
    • suggestMessage

      void suggestMessage(int piece)
      BEP 6 Suggest. Treated as a one-shot request hint: the peer is hinting that this piece is a good one to request from it. Request it immediately if we want it and are unchoked. Never recorded as a permanent have, as that would distort availability for the swarm.
      Parameters:
      piece - the zero-based index of the piece being suggested
    • haveMessage

      void haveMessage(boolean isAll)
      BEP 6.
      Parameters:
      isAll - true for have_all, false for have_none
    • rejectMessage

      void rejectMessage(int piece, int begin, int length)
      BEP 6.
      Parameters:
      piece - the zero-based index of the piece being acted on
      begin - the byte offset into the piece where the rejected block starts
      length - the size of the rejected block in bytes
    • allowedFastMessage

      void allowedFastMessage(int piece)
      Handle an allowed fast message (BEP 6): the peer will serve this piece even while it chokes us. Store it and pull a request now, while we are still choked.

      Indices outside the accepted derivations (see expectedAllowedFast()) are dropped, defending against buggy or malicious senders that invent pieces. Membership bounds what such a sender can get us to request while choked to at most the union size.

      Parameters:
      piece - the piece index
    • expectedAllowedFast

      Set<Integer> expectedAllowedFast()
      The set of piece indices a conforming sender may advertise to us, or null when nothing can be derived. The union of two derivations:
      • our own destination-hash-based set - matches other I2PSnark peers
      • the libtorrent-style set derived from a zeroed IPv4 address - qBittorrent and other libtorrent clients over I2P see 0.0.0.0 as their remote address for every I2P connection, so their advertised set is torrent-wide and computable by anyone

      Membership is still enforced: indices outside this union are dropped, bounding what a malicious peer can get us to request while choked to at most the union size, and the requests remain subject to normal pipeline limits.

      Computed once per connection; called only from the reader thread.

      Returns:
      the expected set, or null for no verification
    • isAllowedFast

      boolean isAllowedFast(int piece)
      Whether the piece is in our BEP 6 allowed fast set for this peer.
      Parameters:
      piece - the piece index
      Returns:
      true if servable even while we choke the peer
    • sendAllowedFast

      void sendAllowedFast(BitField ourBitfield)
      Generate and queue the BEP 6 allowed fast set for this peer.

      The set is derived from the peer's destination hash, so both ends compute the same pieces. Only pieces we actually have are advertised, and requests for them are served even while we choke the peer.

      Parameters:
      ourBitfield - the pieces we have, may be null
    • generateAllowedFastSet

      static Set<Integer> generateAllowedFastSet(byte[] peerHash, byte[] infohash, int pieces)
      Generate the BEP 6 allowed fast set for a peer.

      The first four bytes of the peer's destination hash stand in for the masked IP address, so both ends of an I2P connection can compute the same up to ten piece indices from the torrent infohash.

      Parameters:
      peerHash - the 32-byte destination hash of the peer
      infohash - the infohash of the torrent
      pieces - the number of pieces in the torrent
      Returns:
      the allowed fast set, never null
    • unknownMessage

      void unknownMessage(int type, byte[] bs)
      Handle an unknown message type from the peer.
      Parameters:
      type - the message type byte
      bs - the message payload
    • havePiece

      void havePiece(int piece)
      We now have this piece. Tell the peer and cancel any requests for the piece.
      Parameters:
      piece - the zero-based index of the piece we now hold in full
    • cancelPiece

      void cancelPiece(int piece)
      Tell the other side that we are no longer interested in any of the outstanding requests (if any) for this piece.
      Parameters:
      piece - the zero-based index of the piece whose requests are dropped
    • request

      private void request(boolean resend)
      Starts or resumes requesting pieces.
      Parameters:
      resend - should we resend outstanding requests?
    • addRequest

      void addRequest()
      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.

      This is called from several places:

        By getOutstandingRequest() 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()
      
    • requestNextPiece

      private boolean requestNextPiece(Set<Integer> allowed)
      Starts requesting first chunk of next piece. Returns true if something has been added to the requests, false otherwise. Caller should synchronize.
      Parameters:
      allowed - the pieces servable while choked, or null for no restriction
      Returns:
      true if a request was added for this peer, false if nothing was requested
    • setInteresting

      void setInteresting(boolean interest)
      Whether we are interested in pieces from this peer.
      Parameters:
      interest - true if interested
    • setChoking

      void setChoking(boolean choke)
      Whether we are choking the peer.
      Parameters:
      choke - true to choke, false to unchoke
    • keepAlive

      void keepAlive()
      Send a keepalive message to the peer.
    • retransmitRequests

      void retransmitRequests()
      Retransmit outstanding requests if necessary. Only call if !supportsFast()
    • getRequests

      String getRequests()
      Debug string for the peer, or null.
      Returns:
      string or null