java.lang.Object
org.klomp.snark.TrackerClient
All Implemented Interfaces:
Runnable

public class TrackerClientextends Object implements Runnable
Handles communication with BitTorrent trackers and DHT to discover new peers.

This class manages tracker announcements and peer discovery by:

  • Announcing torrent status to HTTP/HTTPS trackers
  • Processing tracker responses and extracting peer information
  • Managing UDP tracker communication
  • Integrating with DHT for trackerless peer discovery
  • Handling Peer Exchange (PEX) when available
  • Managing announcement intervals and retry logic
  • Processing various tracker events (started, completed, stopped)

Threading Model: The start() method creates a thread that runs one complete announcement cycle through all trackers, PEX, and DHT, then queues a new timed event and exits. This approach prevents thread accumulation while ensuring regular announcements.

The client can be restarted after halt(boolean) and handles various tracker responses including registration failures and error conditions.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    private class 
    DHT peer lookup and announce, run off the announce loop thread so a slow or unreachable DHT cannot delay HTTP tracker announces or PEX.
    private class 
    Queues the next announce on the shared timer.
    private static class 
    One tracker's URL and per-tracker state.
    private class 
    Send "stopped" to a single tracker.
    private static class 
    UDP before HTTP.
  • Field Summary

    Modifier and Type
    Field
    Description
    private int
    Peers seen in the last DHT fetch, updated by that thread.
    private Thread
    The running DHT fetch thread, kept for interrupts.
    The queued timer event, kept for cancellation.
    private boolean
    Set to unannounce from the trackers early.
    private boolean
    Set once the trackers are registered.
    private final Log
    Logger for this torrent's tracker traffic.
    private int
    Number of loop runs so far.
    private long
    When this client was started.
    private Thread
    The running tracker thread, kept for interrupts.
    private final String
    Name prefix for this torrent's threads.
    private static final Map<String, Deque<Long>>
    Recent request times per tracker, for spacing requests.
    Released when this client's last unannounce dispatch finishes; awaited by halt() callers.
    private static final ThreadPoolExecutor
    Unannounces are sent from a shared, bounded pool instead of one thread per tracker, so stopping thousands of torrents cannot exhaust threads.
    private final I2PSnarkUtil
    Util providing the session and proxy settings.
    private final String
    Extra tracker from the user's config, may be null.
    private static final Semaphore
    Global cap on tracker HTTP requests in flight across ALL torrents, so simultaneous announce/scrape rounds cannot stampede a tracker (or our own netdb lookup path) when many TrackerClient threads wake together.
    Backup trackers used when the others fail.
    private boolean
    Whether the torrent is complete, not necessarily seeding, as files may be skipped.
    private static final String
    Event sent when the download finishes.
    private int
    Consecutive failed requests in the loop.
    private final PeerCoordinator
    Coordinator sharing peers with other torrents.
    private static final int
    UDP tracker port used when none is given.
    private static final int
    Tracker requests are spaced at least 2 seconds apart.
    private static final int
    Random delay added to the minimum, up to 6 seconds.
    private static final int
    Random jitter added to MIN_DHT_ANNOUNCE_INTERVAL so DHT announces from many torrents do not fire in lockstep; the effective threshold is 15m..19m.
    private static final int
    No guidance in BEP 5; standard practice is K (=8).
    private static final int
    A DHT with fewer than this many known nodes is still bootstrapping and unreliable, so a seeding trackerless torrent falls back to its backup trackers to find peers.
    private static final String
    Error when the tracker returns an HTML page instead of data.
    private final String
    Hex info hash identifying the torrent.
    private static final int
    A failed registration is retried after 90 seconds.
    private long
    When the last DHT announce ran, written by the fetch thread.
    private boolean
    Whether the last response carried fresh metadata.
    private static final int
    Sleep after lots of fails — shortened from 10m so post-recovery tracker retries fire within ~2m instead of waiting a full cycle.
    private static final int
    Max random spread added to the LONG_SLEEP failure floor so torrents that failed together don't re-announce in lockstep @since 0.9.71+.
    (package private) static final int
    First-announce delay for a torrent with no metainfo (a magnet, including every zzzot name lookup).
    private static final int
    Tracker requests slow down after this many consecutive failures.
    private static final int
    Per-tracker throttle: max 20 requests per tracker host per minute.
    private static final int
    Failed registrations before giving up on a tracker.
    private static final int
    Trackers used per torrent at most.
    private MetaInfo
    The torrent's metadata, reassigned by reinitialize.
    private static final long
    Shortest interval between DHT tracker announces, in milliseconds.
    private static final long
    Shortest interval between HTTP tracker announces, in milliseconds.
    private static final String
    Event value when no event is being reported.
    private static final String
    Tracker error for an unregistered torrent, from ByteMonsoon.
    private static final String
    Tracker error for an unregistered torrent, from DifTracker.
    private static final String
    Tracker error for an unregistered torrent, from Vuze.
    private static final String
    Event sent when the torrent is paused.
    private final String
    Random peer id sent to the trackers.
    private final int
    Listen port advertised to the trackers.
    static final int
    Default BitTorrent tracker port.
    private boolean
    Set while the loop body is running.
    private static final String
    BEP 48 scrape path segment.
    private static final long
    After a scrape with a non-zero swarm size, defer the next scrape this long.
    private static final long
    Periodic scrape interval: refresh the swarm size (seeds + leeches) between announces (BEP 15/48).
    private static final int
    Random jitter added to each scheduled scrape so scrapes from many torrents to a shared tracker spread out instead of firing in lockstep.
    private static final int
    Minutes between tracker status checks.
    private final Snark
    The snark downloading this torrent.
    private boolean
    Set once the started event has been sent.
    private static final String
    Event sent when the torrent starts downloading.
    private boolean
    Set to stop the tracker loop.
    private static final String
    Event sent when the torrent stops.
    private static final long
    Requests to one tracker are spaced across a 1 minute window.
    The active trackers for this torrent.
    private static final long
    An unannounce thread exits after 1 minute idle.
    private static final long
    An unannounce waits at most 10 seconds for a session.
    private static final long
    Dispatch window is scaled to the batch, clamped between these.
    private static final int
    When the queue is this many waves deep, only the primary tracker per torrent gets a slot.
    private static final int
    Unannounce requests held before they are dropped.
    private static final int
    Threads draining the unannounce queue.
    private static final long
    One wave of unannounces, latency-bound, through an established tunnel.
  • Constructor Summary

    Constructors
    Constructor
    Description
    TrackerClient(I2PSnarkUtil util, MetaInfo meta, String additionalTrackerURL, PeerCoordinator coordinator, Snark snark)
    Call start() to start it.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Wait, capped at ms, for the unannounces submitted by the last halt() to dispatch.
    private TrackerInfo
    doRequest(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event)
    Announce - blocking, dispatches to HTTP or UDP by tracker type.
    private TrackerInfo
    doRequestHTTP(TrackerClient.TCTracker tr, String infoHash, String peerID, long uploaded, long downloaded, long left, int event)
    HTTP - blocking.
    private TrackerInfo
    doRequestUDP(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event)
    UDP - blocking.
    private TrackerInfo
    Scrape for swarm composition from a tracker, BEP 15 (UDP) or BEP 48 (HTTP).
    private TrackerInfo
    Scrape for swarm composition from an HTTP tracker, BEP 48: the last path segment of the announce URL is replaced with "scrape" and any query string is dropped.
    private static String
    eventToString(int event)
    The HTTP announce event name for a UDP-style event code.
    private void
    Start a DHT peer fetch in a separate thread, if one is not already running.
    private Hash
    This also validates the URL.
    private int
    Fetch peers from Peer Exchange (PEX).
    private int
    Fetch peers from a list of trackers.
    private int
    Valid web peers from the metainfo.
    void
    halt(boolean fast)
    Interrupts this Thread to stop it.
    boolean
    Check if this tracker client has been halted.
    (package private) static long
    initialAnnounceDelayMs(boolean hasMeta, int fullSpread, int magnetSpread)
    Delay before a torrent's first announce.
    private static boolean
    isMyHash(String key, byte[] ih)
    Whether the key was already in the dictionary.
    private boolean
    isNewValidTracker(Set<Hash> existing, String ann)
    Whether the announce URL already exists in the list.
    (package private) static boolean
    isRegistrationFailure(String trackerProblem)
    Whether a tracker problem means this tracker will never accept the torrent, as opposed to a transient failure worth retrying.
    static boolean
    Whether the announce URL is an I2P host.
    (package private) static long
    longSleepWithJitter(long currentInterval, long randomJitter)
    Failure backoff for a tracker that just hit MAX_CONSEC_FAILS: floor the announce interval at LONG_SLEEP and add up to LONG_SLEEP_JITTER of randomness so many torrents failing together don't re-announce in lockstep every 2 minutes.
    private void
    Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event.
    (package private) static boolean
    needBackupTrackers(boolean havePrimary, boolean haveBackup, boolean dhtEnabled, int dhtSize, boolean downloading)
    Whether a trackerless torrent should consult its backup trackers this round.
    (package private) static String
    Lower-cases a tracker problem message for prefix matching.
    private boolean
    Handle a successful scrape response: refresh the swarm composition and clear the tracker's error indicator — the response proves the tracker is reachable, so a "No response from..." announce failure no longer applies.
    private void
    queueLoop(long delay)
    Queues the next loop run after the delay.
    void
    Call after editing torrent.
    void
    run()
    Run the tracker client thread.
    private void
    Schedule the next scrape for a tracker: the standard interval, or the longer deferral after a good response, plus random jitter so scrapes from many torrents to a shared tracker do not fire in lockstep.
    (package private) static String
    scrapeURL(String announce)
    Build the BEP 48 scrape URL from the announce URL: drop any query string and replace the last path segment with "scrape".
    private void
    Do this one time only (not every time it is started).
    (package private) static boolean
    shouldTryFallbackTrackers(int peersFromPrimaries, boolean haveBackup, boolean downloading)
    Should the fallback trackers be tried after a round that produced no peers?
    private boolean
    smallAnnounce(long left, int event)
    The announce needs few peers, either the download is complete, we are stopping, or we do not need outbound peers.
    void
    Start the tracker client thread.
    boolean
    Check if this tracker client has been started.
    private static void
    Block until this tracker host has capacity under the 20/min throttle.
    private void
    Try to connect to the given peers, spreading the connection attempts over time.
    private void
    Creates a thread for each tracker in parallel if tunnel is still open.
    (package private) static long
    How long a stop should keep its sessions open for unannounces to dispatch: one wave per 32-tracker batch, so small swarms wait ~3s and bigger swarms scale up to UNANNOUNCE_MAX_WAIT.
    private void
    Clamp the tracker interval to the minimum and record it.
    private void
    Update the torrent-level tracker problem state.
    static String
    urlencode(byte[] bs)
    Very lazy byte[] to URL encoder.
    private boolean
    Checks that the session is usable for a request.

    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 torrent's tracker traffic.
    • NO_EVENT

      private static final String NO_EVENT
      Event value when no event is being reported.
      See Also:
    • STARTED_EVENT

      private static final String STARTED_EVENT
      Event sent when the torrent starts downloading.
      See Also:
    • COMPLETED_EVENT

      private static final String COMPLETED_EVENT
      Event sent when the download finishes.
      See Also:
    • STOPPED_EVENT

      private static final String STOPPED_EVENT
      Event sent when the torrent stops.
      See Also:
    • PAUSED_EVENT

      private static final String PAUSED_EVENT
      Event sent when the torrent is paused.
      See Also:
    • NOT_REGISTERED

      private static final String NOT_REGISTERED
      Tracker error for an unregistered torrent, from ByteMonsoon.
      See Also:
    • NOT_REGISTERED_2

      private static final String NOT_REGISTERED_2
      Tracker error for an unregistered torrent, from DifTracker.
      See Also:
    • NOT_REGISTERED_3

      private static final String NOT_REGISTERED_3
      Tracker error for an unregistered torrent, from Vuze.
      See Also:
    • ERROR_GOT_HTML

      private static final String ERROR_GOT_HTML
      Error when the tracker returns an HTML page instead of data.
      See Also:
    • SCRAPE

      private static final String SCRAPE
      BEP 48 scrape path segment.
      See Also:
    • SLEEP

      private static final int SLEEP
      Minutes between tracker status checks.
      See Also:
    • UNANNOUNCE_THREADS

      private static final int UNANNOUNCE_THREADS
      Threads draining the unannounce queue.
      See Also:
    • UNANNOUNCE_QUEUE

      private static final int UNANNOUNCE_QUEUE
      Unannounce requests held before they are dropped.
      See Also:
    • UNANNOUNCE_KEEPALIVE

      private static final long UNANNOUNCE_KEEPALIVE
      An unannounce thread exits after 1 minute idle.
      See Also:
    • UNANNOUNCE_WAVE

      private static final long UNANNOUNCE_WAVE
      One wave of unannounces, latency-bound, through an established tunnel.
      See Also:
    • UNANNOUNCE_MIN_WAIT

      private static final long UNANNOUNCE_MIN_WAIT
      Dispatch window is scaled to the batch, clamped between these.
      See Also:
    • UNANNOUNCE_MAX_WAIT

      private static final long UNANNOUNCE_MAX_WAIT
      An unannounce waits at most 10 seconds for a session.
      See Also:
    • UNANNOUNCE_PRIMARY_THRESHOLD

      private static final int UNANNOUNCE_PRIMARY_THRESHOLD
      When the queue is this many waves deep, only the primary tracker per torrent gets a slot.
      See Also:
    • _unannouncers

      private static final ThreadPoolExecutor _unannouncers
      Unannounces are sent from a shared, bounded pool instead of one thread per tracker, so stopping thousands of torrents cannot exhaust threads. Threads exist only while unannounces are pending and die back after UNANNOUNCE_KEEPALIVE of idle time; the pool is capped at UNANNOUNCE_THREADS, sized to saturate the I2P tunnel (requests are latency-bound, so a few dozen workers drain a burst as fast as the network allows). The bounded queue absorbs the burst; only when the queue and pool are full is the unannounce skipped and the tracker reset, the same degradation as the previous OutOfMemoryError fallback. Threads are daemons, so they never hold up router shutdown.
    • _unannounceLatch

      private volatile CountDownLatch _unannounceLatch
      Released when this client's last unannounce dispatch finishes; awaited by halt() callers.
    • DELAY_MIN

      private static final int DELAY_MIN
      Tracker requests are spaced at least 2 seconds apart.
      See Also:
    • DELAY_RAND

      private static final int DELAY_RAND
      Random delay added to the minimum, up to 6 seconds.
      See Also:
    • MAX_REGISTER_FAILS

      private static final int MAX_REGISTER_FAILS
      Failed registrations before giving up on a tracker.
      See Also:
    • INITIAL_SLEEP

      private static final int INITIAL_SLEEP
      A failed registration is retried after 90 seconds.
      See Also:
    • MAGNET_INITIAL_SLEEP

      static final int MAGNET_INITIAL_SLEEP
      First-announce delay for a torrent with no metainfo (a magnet, including every zzzot name lookup). Short enough that a lookup budget is not spent asleep, long enough that a batch of magnets added at once still does not announce in lockstep.
      See Also:
    • MAX_CONSEC_FAILS

      private static final int MAX_CONSEC_FAILS
      Tracker requests slow down after this many consecutive failures.
      See Also:
    • ANNOUNCE_PERMITS

      private static final Semaphore ANNOUNCE_PERMITS
      Global cap on tracker HTTP requests in flight across ALL torrents, so simultaneous announce/scrape rounds cannot stampede a tracker (or our own netdb lookup path) when many TrackerClient threads wake together. Requests beyond the cap queue on the permit; the fetch timeout clock only starts once the permit is held. Increased from 3 to 10 to allow parallel DHT metadata lookups (e.g. zzzot hashlist) while per-tracker throttle below prevents hammering any single tracker.
    • MAX_PER_TRACKER_PER_MINUTE

      private static final int MAX_PER_TRACKER_PER_MINUTE
      Per-tracker throttle: max 20 requests per tracker host per minute. Shared across all torrents to cap load on popular trackers (e.g. postman, torrfreedom) even when many torrents use the same tracker. Guarded by itself.
      See Also:
    • TRACKER_THROTTLE_WINDOW_MS

      private static final long TRACKER_THROTTLE_WINDOW_MS
      Requests to one tracker are spaced across a 1 minute window.
      See Also:
    • _trackerRequestTimes

      private static final Map<String, Deque<Long>> _trackerRequestTimes
      Recent request times per tracker, for spacing requests.
    • LONG_SLEEP

      private static final int LONG_SLEEP
      Sleep after lots of fails — shortened from 10m so post-recovery tracker retries fire within ~2m instead of waiting a full cycle.
      See Also:
    • LONG_SLEEP_JITTER

      private static final int LONG_SLEEP_JITTER
      Max random spread added to the LONG_SLEEP failure floor so torrents that failed together don't re-announce in lockstep @since 0.9.71+.
      See Also:
    • MIN_TRACKER_ANNOUNCE_INTERVAL

      private static final long MIN_TRACKER_ANNOUNCE_INTERVAL
      Shortest interval between HTTP tracker announces, in milliseconds.
      See Also:
    • MIN_DHT_ANNOUNCE_INTERVAL

      private static final long MIN_DHT_ANNOUNCE_INTERVAL
      Shortest interval between DHT tracker announces, in milliseconds.
      See Also:
    • DHT_ANNOUNCE_JITTER

      private static final int DHT_ANNOUNCE_JITTER
      Random jitter added to MIN_DHT_ANNOUNCE_INTERVAL so DHT announces from many torrents do not fire in lockstep; the effective threshold is 15m..19m.
      See Also:
    • SCRAPE_INTERVAL

      private static final long SCRAPE_INTERVAL
      Periodic scrape interval: refresh the swarm size (seeds + leeches) between announces (BEP 15/48). The announce loop wakes every 5m, so this fires on every other wake per active torrent.
      See Also:
    • SCRAPE_GOOD_DEFER

      private static final long SCRAPE_GOOD_DEFER
      After a scrape with a non-zero swarm size, defer the next scrape this long.
      See Also:
    • SCRAPE_JITTER

      private static final int SCRAPE_JITTER
      Random jitter added to each scheduled scrape so scrapes from many torrents to a shared tracker spread out instead of firing in lockstep.
      See Also:
    • DHT_ANNOUNCE_PEERS

      private static final int DHT_ANNOUNCE_PEERS
      No guidance in BEP 5; standard practice is K (=8).
      See Also:
    • DHT_BOOTSTRAP_NODES

      private static final int DHT_BOOTSTRAP_NODES
      A DHT with fewer than this many known nodes is still bootstrapping and unreliable, so a seeding trackerless torrent falls back to its backup trackers to find peers.
      See Also:
    • PORT

      public static final int PORT
      Default BitTorrent tracker port.
      See Also:
    • DEFAULT_UDP_TRACKER_PORT

      private static final int DEFAULT_UDP_TRACKER_PORT
      UDP tracker port used when none is given.
      See Also:
    • MAX_TRACKERS

      private static final int MAX_TRACKERS
      Trackers used per torrent at most.
      See Also:
    • _util

      private final I2PSnarkUtil _util
      Util providing the session and proxy settings.
    • meta

      private MetaInfo meta
      The torrent's metadata, reassigned by reinitialize.
    • infoHash

      private final String infoHash
      Hex info hash identifying the torrent.
    • peerID

      private final String peerID
      Random peer id sent to the trackers.
    • additionalTrackerURL

      private final String additionalTrackerURL
      Extra tracker from the user's config, may be null.
    • coordinator

      private final PeerCoordinator coordinator
      Coordinator sharing peers with other torrents.
    • snark

      private final Snark snark
      The snark downloading this torrent.
    • port

      private final int port
      Listen port advertised to the trackers.
    • _threadName

      private final String _threadName
      Name prefix for this torrent's threads.
    • stop

      private volatile boolean stop
      Set to stop the tracker loop.
    • started

      private volatile boolean started
      Set once the started event has been sent.
    • _initialized

      private volatile boolean _initialized
      Set once the trackers are registered.
    • _runCount

      private volatile int _runCount
      Number of loop runs so far.
    • _thread

      private volatile Thread _thread
      The running tracker thread, kept for interrupts.
    • _dhtThread

      private volatile Thread _dhtThread
      The running DHT fetch thread, kept for interrupts.
    • _dhtPeersSeen

      private volatile int _dhtPeersSeen
      Peers seen in the last DHT fetch, updated by that thread.
    • _event

      private volatile SimpleTimer2.TimedEvent _event
      The queued timer event, kept for cancellation.
    • runStarted

      private volatile boolean runStarted
      Set while the loop body is running.
    • consecutiveFails

      private volatile int consecutiveFails
      Consecutive failed requests in the loop.
    • lastHadMeta

      private volatile boolean lastHadMeta
      Whether the last response carried fresh metadata.
    • completed

      private boolean completed
      Whether the torrent is complete, not necessarily seeding, as files may be skipped.
    • _fastUnannounce

      private volatile boolean _fastUnannounce
      Set to unannounce from the trackers early.
    • lastDHTAnnounce

      private volatile long lastDHTAnnounce
      When the last DHT announce ran, written by the fetch thread.
    • trackers

      private final List<TrackerClient.TCTracker> trackers
      The active trackers for this torrent.
    • backupTrackers

      private final List<TrackerClient.TCTracker> backupTrackers
      Backup trackers used when the others fail.
    • _startedOn

      private long _startedOn
      When this client was started.
  • Constructor Details

    • TrackerClient

      public TrackerClient(I2PSnarkUtil util, MetaInfo meta, String additionalTrackerURL, PeerCoordinator coordinator, Snark snark)
      Call start() to start it.
      Parameters:
      util - the I2PSnarkUtil instance shared with the rest of the client
      meta - null if in magnet mode
      additionalTrackerURL - may be null, from the ?tr= param in magnet mode, otherwise ignored
      coordinator - the peer coordinator supplying completion state and requesting outbound peers
      snark - the Snark for this torrent, supplying the info hash and peer ID
  • Method Details

    • unannounceDispatchWait

      static long unannounceDispatchWait()
      How long a stop should keep its sessions open for unannounces to dispatch: one wave per 32-tracker batch, so small swarms wait ~3s and bigger swarms scale up to UNANNOUNCE_MAX_WAIT.
      Returns:
      the wait in milliseconds, never below UNANNOUNCE_MIN_WAIT and never above UNANNOUNCE_MAX_WAIT
    • throttleForTracker

      private static void throttleForTracker(String host)
      Block until this tracker host has capacity under the 20/min throttle. Must be called before acquiring ANNOUNCE_PERMITS so queued threads don't hold global permits while waiting for per-tracker quota.
      Parameters:
      host - tracker host (from TCTracker.host), nullable
    • longSleepWithJitter

      static long longSleepWithJitter(long currentInterval, long randomJitter)
      Failure backoff for a tracker that just hit MAX_CONSEC_FAILS: floor the announce interval at LONG_SLEEP and add up to LONG_SLEEP_JITTER of randomness so many torrents failing together don't re-announce in lockstep every 2 minutes. Intervals already at or above the floor and out-of-range random values are handled: the jitter is clamped to [0, LONG_SLEEP_JITTER] and only applied when the floor was raised.
      Parameters:
      currentInterval - tracker's current announce interval in ms
      randomJitter - raw random jitter in ms, before clamping
      Returns:
      the new announce interval in ms
    • start

      public void start()
      Start the tracker client thread.
    • halted

      public boolean halted()
      Check if this tracker client has been halted.
      Returns:
      true if halted
    • started

      public boolean started()
      Check if this tracker client has been started.
      Returns:
      true if started
    • halt

      public void halt(boolean fast)
      Interrupts this Thread to stop it.
      Parameters:
      fast - if true, limit the life of the unannounce threads
    • awaitUnannounces

      public void awaitUnannounces(long ms)
      Wait, capped at ms, for the unannounces submitted by the last halt() to dispatch. Returns immediately when nothing was submitted or they are done.
      Parameters:
      ms - the longest to wait, in milliseconds, for the last halt()'s
    • queueLoop

      private void queueLoop(long delay)
      Queues the next loop run after the delay.
      Parameters:
      delay - the seconds to wait
    • verifyConnected

      private boolean verifyConnected()
      Checks that the session is usable for a request.
      Returns:
      true if the session is connected
    • run

      public void run()
      Run the tracker client thread.
      Specified by:
      run in interface Runnable
    • reinitialize

      public void reinitialize()
      Call after editing torrent.
    • setup

      private void setup()
      Do this one time only (not every time it is started). Unless torrent was edited.
    • isNewValidTracker

      private boolean isNewValidTracker(Set<Hash> existing, String ann)
      Whether the announce URL already exists in the list.
      Parameters:
      existing - the ones we already know about
      ann - an announce URL non-null
      Returns:
      true if ann is valid and new; adds to existing if returns true
    • loop

      private void loop()
      Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event. This will take several seconds to several minutes.
    • normalizeTrackerProblem

      static String normalizeTrackerProblem(String trackerProblem)
      Lower-cases a tracker problem message for prefix matching.

      Never null: several IOException subtypes return a null getMessage(), and the announce loop calls this on the result directly. Letting that NPE escape would abort the announce for every remaining tracker because of one malformed response from the first one.

      Parameters:
      trackerProblem - message from the exception, may be null
      Returns:
      lower-cased message, or the empty string when there was none
    • isRegistrationFailure

      static boolean isRegistrationFailure(String trackerProblem)
      Whether a tracker problem means this tracker will never accept the torrent, as opposed to a transient failure worth retrying.
      Parameters:
      trackerProblem - message already passed through normalizeTrackerProblem(String), never null
      Returns:
      true if the tracker is rejecting the torrent outright
    • initialAnnounceDelayMs

      static long initialAnnounceDelayMs(boolean hasMeta, int fullSpread, int magnetSpread)
      Delay before a torrent's first announce.

      Pure decision, separated so the magnet case can be pinned by test: the spread exists to protect the trackers from a thundering herd, and that concern does not apply to a single lookup, which is actively waiting for an answer.

      Parameters:
      hasMeta - true once the torrent has its metainfo
      fullSpread - random value in [0, INITIAL_SLEEP)
      magnetSpread - random value in [0, MAGNET_INITIAL_SLEEP)
      Returns:
      ms to wait before the first announce
    • shouldTryFallbackTrackers

      static boolean shouldTryFallbackTrackers(int peersFromPrimaries, boolean haveBackup, boolean downloading)
      Should the fallback trackers be tried after a round that produced no peers?

      needBackupTrackers(boolean, boolean, boolean, int, boolean) deliberately refuses to consult backups while a primary tracker is configured, which is right for a download that is already finding peers but leaves a magnet with nothing: every announce failed and the next cycle is 90s away. A different transport is the only remaining option, and the caller has already paid for the round that failed.

      Parameters:
      peersFromPrimaries - peers returned by the primary trackers this cycle
      haveBackup - whether any fallback tracker is configured
      downloading - whether the torrent still wants peers
      Returns:
      true if the fallback round should run
    • needBackupTrackers

      static boolean needBackupTrackers(boolean havePrimary, boolean haveBackup, boolean dhtEnabled, int dhtSize, boolean downloading)
      Whether a trackerless torrent should consult its backup trackers this round. A download always does; a seed does only while the DHT is still short of its bootstrap node count, since once the DHT is warm the trackers add nothing.
      Parameters:
      havePrimary - whether any primary tracker is configured
      haveBackup - whether any fallback tracker is configured
      dhtEnabled - whether a DHT instance is running for this torrent
      dhtSize - the number of known nodes in that DHT
      downloading - whether the torrent still wants peers, false for a seed
      Returns:
      true if the backup tracker round should run
    • getPeersFromTrackers

      private int getPeersFromTrackers(List<TrackerClient.TCTracker> trckrs) throws Throwable
      Fetch peers from a list of trackers.
      Parameters:
      trckrs - the list of trackers to query
      Returns:
      max peers seen
      Throws:
      Throwable - from a tracker announce or scrape, e.g. I/O or protocol errors
    • updateTrackerProblems

      private void updateTrackerProblems()
      Update the torrent-level tracker problem state. Problems are only reported when announces to every active tracker have failed; a single working tracker clears the error state.
    • processScrapeResponse

      private boolean processScrapeResponse(TrackerClient.TCTracker tr, TrackerInfo scrape)
      Handle a successful scrape response: refresh the swarm composition and clear the tracker's error indicator — the response proves the tracker is reachable, so a "No response from..." announce failure no longer applies.
      Parameters:
      tr - the tracker that answered, its error indicator is cleared
      scrape - the parsed scrape response carrying the swarm counts
      Returns:
      true if the response reported a non-zero swarm size
    • scheduleNextScrape

      private void scheduleNextScrape(TrackerClient.TCTracker tr, boolean good)
      Schedule the next scrape for a tracker: the standard interval, or the longer deferral after a good response, plus random jitter so scrapes from many torrents to a shared tracker do not fire in lockstep.
      Parameters:
      tr - the tracker record whose nextScrapeTime is set
      good - true if the last response reported a non-zero swarm size
    • getPeersFromPEX

      private int getPeersFromPEX()
      Fetch peers from Peer Exchange (PEX).
      Returns:
      max peers seen
    • fetchPeersFromDHT

      private void fetchPeersFromDHT()
      Start a DHT peer fetch in a separate thread, if one is not already running. The lookup and announce can take minutes, so it must not block the announce loop.
    • getWebPeers

      private int getWebPeers()
      Valid web peers from the metainfo.
      Returns:
      valid web peers from metainfo
    • tryAddPeers

      private void tryAddPeers(Collection<Peer> peers)
      Try to connect to the given peers, spreading the connection attempts over time. The peers are shuffled first so no single peer is always first; the passed collection is not modified.
      Parameters:
      peers - the peers to try to connect to
    • unannounce

      private void unannounce()
      Creates a thread for each tracker in parallel if tunnel is still open.
    • doRequest

      private TrackerInfo doRequest(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) throws IOException, Throwable
      Announce - blocking, dispatches to HTTP or UDP by tracker type.

      Note: IOException message text gets displayed in the UI

      Parameters:
      tr - the tracker to announce to, its isUDP flag picks the transport
      uploaded - bytes uploaded by this client
      downloaded - bytes downloaded by this client
      left - bytes remaining to download, -1 in magnet mode
      event - the BEP-3 announce event, e.g. started, completed or stopped
      Returns:
      null if _fastUnannounce && event == STOPPED
      Throws:
      IOException - if the HTTP announce fails, the message shows in the UI
      Throwable - if the UDP announce fails
    • doScrape

      private TrackerInfo doScrape(TrackerClient.TCTracker tr) throws IOException
      Scrape for swarm composition from a tracker, BEP 15 (UDP) or BEP 48 (HTTP).
      Parameters:
      tr - the tracker record, whose isUDP flag selects the BEP 15 or BEP 48 path
      Returns:
      the scrape result, or null on failure
      Throws:
      IOException - on HTTP scrape failure
    • doScrapeHTTP

      private TrackerInfo doScrapeHTTP(TrackerClient.TCTracker tr, String infoHash) throws IOException
      Scrape for swarm composition from an HTTP tracker, BEP 48: the last path segment of the announce URL is replaced with "scrape" and any query string is dropped.
      Parameters:
      tr - the tracker record, supplying the announce URL and the host name
      infoHash - the hex info hash
      Returns:
      the scrape result, or null on failure
      Throws:
      IOException - on fetch or parse failure
    • scrapeURL

      static String scrapeURL(String announce)
      Build the BEP 48 scrape URL from the announce URL: drop any query string and replace the last path segment with "scrape". A trailing slash is stripped first, so the last non-empty segment is replaced.
      Parameters:
      announce - the announce URL
      Returns:
      the scrape URL
    • isMyHash

      private static boolean isMyHash(String key, byte[] ih)
      Whether the key was already in the dictionary.
      Parameters:
      key - a bencoded dictionary key
      ih - the torrent's info hash
      Returns:
      whether the key is the info hash, byte for byte
    • doRequestHTTP

      private TrackerInfo doRequestHTTP(TrackerClient.TCTracker tr, String infoHash, String peerID, long uploaded, long downloaded, long left, int event) throws IOException, Throwable
      HTTP - blocking.

      Note: IOException message text gets displayed in the UI

      Parameters:
      tr - the tracker to announce to, its announce URL gets the query parameters
      infoHash - the hex info hash of the torrent, sent as the info_hash parameter
      peerID - our peer id, sent as the peer_id parameter
      uploaded - bytes uploaded by this client
      downloaded - bytes downloaded by this client
      left - bytes remaining to download, -1 in magnet mode
      event - the BEP-3 announce event, e.g. started, completed or stopped
      Returns:
      the tracker's parsed announce response, never null
      Throws:
      IOException - if the tracker is unreachable, returned HTML, or reported a failure
      Throwable - if the bencoded response cannot be parsed
    • doRequestUDP

      private TrackerInfo doRequestUDP(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) throws IOException
      UDP - blocking.
      Parameters:
      tr - the tracker to announce to over BEP 15
      uploaded - bytes uploaded by this client
      downloaded - bytes downloaded by this client
      left - bytes remaining to download, -1 in magnet mode
      event - the BEP-3 announce event, e.g. started, completed or stopped
      Returns:
      null if _fastUnannounce && event == STOPPED
      Throws:
      IOException - if the UDP tracker client is unavailable or the exchange fails
    • smallAnnounce

      private boolean smallAnnounce(long left, int event)
      The announce needs few peers, either the download is complete, we are stopping, or we do not need outbound peers.
      Parameters:
      left - the bytes left to download, -1 for magnet
      event - the announce event
      Returns:
      whether the announce should request few or no peers
    • updateInterval

      private void updateInterval(TrackerClient.TCTracker tr, TrackerInfo info)
      Clamp the tracker interval to the minimum and record it.
      Parameters:
      tr - the tracker record whose interval is clamped and recorded
      info - the announce response
    • eventToString

      private static String eventToString(int event)
      The HTTP announce event name for a UDP-style event code.
      Parameters:
      event - the UDP event code
      Returns:
      the event name
    • urlencode

      public static String urlencode(byte[] bs)
      Very lazy byte[] to URL encoder. Just encodes almost everything, even some "normal" chars. By not encoding about 1/4 of the chars, we make random data like hashes about 16% smaller.

      RFC1738: 0-9a-zA-Z$-_.+!*'(), Us: 0-9a-zA-Z

      Parameters:
      bs - the bytes to encode
      Returns:
      the URL-encoded string
    • isValidAnnounce

      public static boolean isValidAnnounce(String ann)
      Whether the announce URL is an I2P host.
      Parameters:
      ann - an announce URL, may be null, returns false if null
      Returns:
      true for i2p hosts only
    • getHostHash

      private Hash getHostHash(String ann)
      This also validates the URL.
      Parameters:
      ann - an announce URL non-null
      Returns:
      a Hash for i2p hosts only, null otherwise