java.lang.Object
net.i2p.router.peermanager.ProfileOrganizer

public class ProfileOrganizerextends Object
Categorizes peers into performance tiers (fast, high-capacity, well-integrated) based on historical metrics from PeerProfile. Requires periodic reorganize() calls to update peer classifications for optimal tunnel selection.

Tier assignment during reorganize(): 1. All profiles are iterated in capacity order (via InverseCapacityComparator) 2. Expired/unreachable/low-bandwidth peers are filtered out 3. Speed, capacity, and integration thresholds are recalculated from the active set 4. Each surviving profile is evaluated via lockedPromoteProfileToTiers() 5. If fast/high-cap tiers fall below minimum thresholds, fallback passes fill gaps (with selectability checks to prevent selecting unusable peers)

A promotion whose RouterInfo has aged past PROP_ROUTERINFO_REFRESH_AGE_MS and which has no proof of life is held back: the peer is left out of the tier until a newer publication arrives on its own. Nothing is fetched on the peer's behalf, unless holding the promotion would starve the tier it would have joined — see shouldDeferPromotionOnStaleRouterInfo(int, int).

Concurrency: ReentrantReadWriteLock protects all tier maps. reorganize() acquires the write lock; selection methods use the read lock. getOrCreateProfileNonblocking(Hash) uses try-lock escalation so a read path never blocks on the write lock.

  • Field Details

    • ATTACK_THRESHOLD

      public static final double ATTACK_THRESHOLD
      Threshold below which the network is considered under attack.
      See Also:
    • CAPACITY_COMPARATOR

      private static final Comparator<PeerProfile> CAPACITY_COMPARATOR
      Sorts profiles by capacity, highest first.
    • STALENESS_THEN_CAPACITY

      private static final Comparator<PeerProfile> STALENESS_THEN_CAPACITY
      Eviction order: least recently active first, then lowest capacity.

      Activity is the newest of last-sent-successfully, last-heard-from and last-heard-about, matching isStaleAbsentPeer(PeerProfile, long, long). Ties fall through to CAPACITY_COMPARATOR so two equally idle peers are separated on what else is known about them rather than on map order.

    • _log

      private final Log _log
      Logger for profile reorganization.
    • _context

      private final RouterContext _context
      The router context.
    • _fastPeers

      private final Map<Hash, PeerProfile> _fastPeers
      Profiles of peers fast enough for tunnels.
    • _fastQualityCount

      private int _fastQualityCount
      Count of fast-tier peers that passed all tests (proven throughput or high-cap bypass). Maintained exclusively via putFastPeer(Hash, PeerProfile) and removeFastPeer(Hash).
    • _profileCount

      private volatile int _profileCount
      Last values published by reorganize(boolean), sampled by CoalesceStatsEvent on the short cycle. Plain reads are enough: these are gauges that hold their last level, and a torn pair would at worst report a count from the previous reorganisation.
    • _qualityCount

      private volatile int _qualityCount
      Profiles rated by the last organize run.
      See Also:
    • _activeProfileCount

      private volatile int _activeProfileCount
      Profiles currently in use by peers.
      See Also:
    • ACTIVE_WINDOW_MS

      private static final long ACTIVE_WINDOW_MS
      Window used by getActiveProfileCount(), in ms.
      See Also:
    • _refresher

      private final RouterInfoRefresher _refresher
      Per-cycle bounds and counters for the promotion deferral on a stale RouterInfo. A cycle is opened by lockedRebuildTiers(Set, double) and its counters are published by recordRouterInfoRefreshStats() before the reorg write lock is released.
    • _highCapacityPeers

      private final Map<Hash, PeerProfile> _highCapacityPeers
      Profiles of peers with high capacity.
    • _promotionSkipTally

      private final Map<String,Integer> _promotionSkipTally
      Skip reasons from the promotion scan in flight, reported by drainPromotionSkipTally().

      Guarded by the write lock every caller already holds; see recordPromotionSkip(String).

    • _lastTierFillScan

      private long _lastTierFillScan
      When promoteToFillTiers() last ran a full scan, in router time.
    • _tiersShort

      private volatile boolean _tiersShort
      Whether either tier was under its target at the last tier-fill scan. Widens the loss-readmit fast-track while short (see lossyReadmitAllowed(float, long, long, long, long, long, float, float, boolean)); recomputed on every scan, so it lags a demotion by at most the fill debounce.
    • TIER_FILL_DEBOUNCE_MS

      static final long TIER_FILL_DEBOUNCE_MS
      Shortest gap between two full tier-refill scans.

      Well under the reorganise interval, so the debounce can never delay a refill past the next cycle that would have done it anyway.

      See Also:
    • _wellIntegratedPeers

      private final Map<Hash, PeerProfile> _wellIntegratedPeers
      Profiles of peers well integrated into the netdb.
    • _notFailingPeers

      private final Map<Hash, PeerProfile> _notFailingPeers
      Profiles of peers not currently failing.
    • _allTiers

      private final List<Map<Hash, PeerProfile>> _allTiers
      Every tier this object retains, in a fixed order, so a pass that must read the whole retained population walks one collection instead of repeating itself per map.

      Assigned in the constructor once all four maps exist. The maps are themselves final and are never replaced, so this never needs rebuilding.

    • _notFailingPeersList

      private final List<Hash> _notFailingPeersList
      Cached keys of the not-failing peers map.
    • _demotedPeers

      private final ConcurrentHashMap<Hash,Long> _demotedPeers
      Peers demoted via demoteIfUnreachable — excluded from promotion for TUNNEL_DEMOTION_COOLDOWN_MS.
    • _demoteStrikes

      private final ConcurrentHashMap<Hash,Integer> _demoteStrikes
      Strike count per peer for demoteIfUnreachable — only demote after DEMOTE_STRIKE_THRESHOLD strikes within the decay window.
    • _demoteStrikeTimes

      private final ConcurrentHashMap<Hash,Long> _demoteStrikeTimes
      Last strike time per peer, so strikes expire after DEMOTE_STRIKE_DECAY_MS.
    • DEMOTE_STRIKE_THRESHOLD

      public static final int DEMOTE_STRIKE_THRESHOLD
      Unreachable first-hop failures needed inside the decay window before demoteIfUnreachable demotes.
      See Also:
    • DEMOTE_STRIKE_DECAY_MS

      public static final long DEMOTE_STRIKE_DECAY_MS
      A strike older than this no longer counts, so a peer that recovered is not demoted by one later failure.
      See Also:
    • MAX_DEMOTED_PEERS

      private static final int MAX_DEMOTED_PEERS
      Demoted peers remembered across reorganizations.
      See Also:
    • _us

      private Hash _us
      Hash of this router.
    • _persistenceHelper

      private final ProfilePersistenceHelper _persistenceHelper
      Saves and restores the peer profiles.
    • _strictCapacityOrder

      private Set<PeerProfile> _strictCapacityOrder
      All profiles ordered by inverse capacity.
    • _thresholdSpeedValue

      private double _thresholdSpeedValue
      Speed value a peer needs for the fast pool.
    • _thresholdCapacityValue

      private double _thresholdCapacityValue
      Capacity value a peer needs for the high-capacity pool.
    • _thresholdIntegrationValue

      private double _thresholdIntegrationValue
      Integration value a peer needs for the well-integrated pool.
    • _thresholdRTT

      private double _thresholdRTT
      Round trip time peers are held to, in ms.
    • _baselineRTT

      private volatile double _baselineRTT
      First-measured fast-tier RTT boundary, captured during the initial reorganize pass. Used by computeAdaptiveRttCeiling(double, double) to floor the adaptive ceiling at 2× the startup baseline when build success is degraded, preventing RTT ceiling amplification during cascade failure.
    • _averageLowLatencyRTT

      private volatile float _averageLowLatencyRTT
      Calibrated cohort latency floor across fast peers with data, computed during each reorganize by computeAverageLowLatencyRTT() and read through getAverageLowLatencyRTT().

      Previously documented as tightening the low-latency threshold to 1.5x the cohort average, but no caller ever consulted it: the value was recomputed every cycle and discarded. It is now calibrated as the mean of the fastest samples and applied as a second bound alongside the per-transport floor.

    • _comp

      private final InverseCapacityComparator _comp
      Comparator ordering profiles by inverse capacity.
    • _rttBaselines

      private final Map<String,Long> _rttBaselines
      Calibrated latency floor per transport style, in ms, published by sampleFirstHopRtts() once a style has enough samples. Absent means that style has no calibrated opinion yet and the absolute rule applies unchanged. Immutable after publication, so reads are lock-free.
    • LOW_LATENCY_BASELINE_MULTIPLE_DEFAULT

      private static final int LOW_LATENCY_BASELINE_MULTIPLE_DEFAULT
      How many times the calibrated floor a peer's latency may sit under and still count as low latency. Three is deliberately permissive: roughly half of all peers fall within two times a top-50 floor, so this admits the fast end of each transport's population and rejects the tail.
      See Also:
    • PROP_LOW_LATENCY_MULTIPLE

      public static final String PROP_LOW_LATENCY_MULTIPLE
      Property key for the calibrated low-latency multiple. @since 0.9.71+.
      See Also:
    • _lowLatencyBaselineMultiple

      private final int _lowLatencyBaselineMultiple
      How many times the calibrated floor a peer's latency may sit under and still count as low latency, read once from PROP_LOW_LATENCY_MULTIPLE. Three is deliberately permissive: roughly half of all peers fall within two times a top-50 floor, so this admits the fast end of each transport's population and rejects the tail.

      Read at construction rather than per decision so the bar cannot shift underneath a cycle, and held at a sane minimum so a misconfigured value cannot make the floor vacuous.

    • PROP_MINIMUM_FAST_PEERS

      public static final String PROP_MINIMUM_FAST_PEERS
      Property key naming the fast tier's refill target.
      See Also:
    • SCALING_THRESHOLD

      static final int SCALING_THRESHOLD
      Network size above which tier thresholds scale with the network instead of staying at their configured default.
      See Also:
    • _defaultMinFastPeers

      public static volatile int _defaultMinFastPeers
      Fast tier refill target, in peers, as last configured or autotuned.
    • PROP_MAX_ROUTERINFO_AGE_HOURS

      public static final String PROP_MAX_ROUTERINFO_AGE_HOURS
      Property key naming the RouterInfo age past which a RouterInfo is treated as stale and must show proof of life to be selected, in hours.
      See Also:
    • DEFAULT_MAX_ROUTERINFO_AGE_HOURS

      public static final int DEFAULT_MAX_ROUTERINFO_AGE_HOURS
      Default RouterInfo age past which a RouterInfo is treated as stale and must show proof of life to be selected, in hours.
      See Also:
    • PROP_ROUTERINFO_REFRESH_AGE_MS

      public static final String PROP_ROUTERINFO_REFRESH_AGE_MS
      Property key naming the RouterInfo age past which a tier promotion is held back, in ms.

      Set to 0 or less to disable the deferral entirely, which restores plain promotion on whatever the netdb holds.

      See Also:
    • DEFAULT_ROUTERINFO_REFRESH_AGE_MS

      public static final long DEFAULT_ROUTERINFO_REFRESH_AGE_MS
      One hour, i.e. one republish interval.
      See Also:
    • STARTUP_GRACE_PERIOD_MS

      private static final long STARTUP_GRACE_PERIOD_MS
      Grace period after startup before peers are penalized.
      See Also:
    • PROOF_OF_LIFE_WINDOW_MS

      private static final long PROOF_OF_LIFE_WINDOW_MS
      Window for trusting a stale router info as proof of life.
      See Also:
    • PROP_MAXIMUM_FAST_PEERS

      public static final String PROP_MAXIMUM_FAST_PEERS
      Property key naming the fast tier's ceiling.
      See Also:
    • _defaultMaxFastPeers

      public static volatile int _defaultMaxFastPeers
      Fast tier ceiling, in peers.
    • PROP_MINIMUM_HIGH_CAPACITY_PEERS

      public static final String PROP_MINIMUM_HIGH_CAPACITY_PEERS
      Property key naming the high-capacity tier's refill target.
      See Also:
    • DEFAULT_MINIMUM_HIGH_CAPACITY_PEERS

      public static final int DEFAULT_MINIMUM_HIGH_CAPACITY_PEERS
      Default high-capacity tier refill target, in peers.
      See Also:
    • _defaultMinHighCapPeers

      public static volatile int _defaultMinHighCapPeers
      High-capacity tier refill target, in peers, as last configured or autotuned.
    • PROP_MAXIMUM_HIGH_CAPACITY_PEERS

      public static final String PROP_MAXIMUM_HIGH_CAPACITY_PEERS
      Property key naming the high-capacity tier's ceiling.
      See Also:
    • _defaultMaxHighCapPeers

      public static volatile int _defaultMaxHighCapPeers
      High-capacity tier ceiling, in peers.
    • MIN_TUNNEL_ACCEPTANCE_RATIO

      private static final double MIN_TUNNEL_ACCEPTANCE_RATIO
      Minimum tunnel acceptance ratio (40%) to remain in high-capacity/fast tiers.
      See Also:
    • MIN_TUNNEL_REQUESTS

      static final int MIN_TUNNEL_REQUESTS
      Lifetime requests a peer needs before its acceptance ratio may exclude it.

      Package-visible because the tiers apply different floors to it and the boundary between "judged" and "not judged" is exactly the difference. The high-capacity tier passes this value and the fast tier passes one, so the same counts give opposite answers on the two. TierAdmissionFloorTest pins that boundary.

      See Also:
    • TUNNEL_DEMOTION_COOLDOWN_MS

      private static final long TUNNEL_DEMOTION_COOLDOWN_MS
      Cooldown period (ms) after demotion before peer can be re-promoted.
      See Also:
    • MAX_LIFETIME_TUNNEL_FAILURES

      private static final long MAX_LIFETIME_TUNNEL_FAILURES
      Exclude peers from tunnel selection after this many cumulative failures OR when the lifetime failure ratio exceeds MAX_LIFETIME_FAILURE_RATIO, whichever is stricter. The hard cap is high (50) to avoid permanently excluding long-lived peers that accumulated failures over days but are currently healthy. Between SOFT_FAILURE_PENALTY_THRESHOLD (20) and this cap, peers are penalized (lower selection priority) but not excluded — a lightweight exponential backoff that still allows recovery.
      See Also:
    • SOFT_FAILURE_PENALTY_THRESHOLD

      private static final long SOFT_FAILURE_PENALTY_THRESHOLD
      Failure count above which peers receive a selection priority penalty but are not excluded. Between this and MAX_LIFETIME_TUNNEL_FAILURES, peers are deprioritized rather than banned.
      See Also:
    • MAX_LIFETIME_FAILURE_RATIO

      private static final double MAX_LIFETIME_FAILURE_RATIO
      Maximum lifetime failure ratio (failed / (agreed + failed)) before a peer is excluded. A peer with 50%+ failure rate is unreliable regardless of absolute count. Combined with MAX_LIFETIME_TUNNEL_FAILURES, this prevents long-lived peers with terrible records from staying selectable.
      See Also:
    • MIN_HC_TIGHT_COUNT

      private static final int MIN_HC_TIGHT_COUNT
      When high-cap tier has at least this many peers, require actual capacity threshold — stop bucket-filling with marginal peers.
      See Also:
    • MIN_FAST_QUALITY_COUNT

      private static final int MIN_FAST_QUALITY_COUNT
      When fast tier has at least this many peers, require all tests passing — peer test (low latency), active, no recent failures, AND proven tunnel throughput. Peers that are low-latency but have never participated in a real tunnel are excluded.
      See Also:
    • MAX_SOFT_PURGE_FLOOR

      static final int MAX_SOFT_PURGE_FLOOR
      Upper bound on the soft-purge tier size, in peers.

      Caps softPurgeThreshold(int) so that on a large network the threshold stays reachable rather than scaling without limit.

      See Also:
    • HIGH_CAP_ADMISSION_REQUIRES_ACCEPTANCE

      static final boolean HIGH_CAP_ADMISSION_REQUIRES_ACCEPTANCE
      Whether high-capacity admission requires acceptable tunnel acceptance. False: high capacity is the reservoir the fast tier is seeded from, so requiring acceptance here would filter it by the same criterion that empties the fast tier, leaving nothing to seed from.
      See Also:
    • FAST_ADMISSION_REQUIRES_ACCEPTANCE

      static final boolean FAST_ADMISSION_REQUIRES_ACCEPTANCE
      Whether fast-tier admission requires acceptable tunnel acceptance. True: the fast tier is the selective tier, and a peer that declines most of our build requests is wasted work wherever it is listed.
      See Also:
    • EVICTION_REQUIRES_ACCEPTANCE_BOTH_TIERS

      static final boolean EVICTION_REQUIRES_ACCEPTANCE_BOTH_TIERS
      Whether acceptance-based eviction applies to both tiers. True: admission is permissive and eviction is what keeps poor accepters from accumulating, so the two must disagree on admission and agree on eviction.
      See Also:
    • PROP_LOSSY_THRESHOLD

      public static final String PROP_LOSSY_THRESHOLD
      Config property for the loss ratio above which a peer is demoted from fast/high-cap tiers.
      See Also:
    • DEFAULT_LOSSY_THRESHOLD

      private static final float DEFAULT_LOSSY_THRESHOLD
      Default loss ratio threshold (20% of packets retransmitted).
      See Also:
    • _tunedLossyThreshold

      private static volatile float _tunedLossyThreshold
      Tuned lossy threshold, -1.0 = use router config. Set by the Tuner when autotuning profileOrganizer.lossyThreshold.
    • PROP_LOSSY_WINDOW

      public static final String PROP_LOSSY_WINDOW
      Config property for how long a reported loss ratio stays fresh.
      See Also:
    • DEFAULT_LOSSY_WINDOW

      private static final long DEFAULT_LOSSY_WINDOW
      Default freshness window in ms (10 minutes).
      See Also:
    • PROP_LOSSY_MIN_PACKETS

      public static final String PROP_LOSSY_MIN_PACKETS
      Config property for the minimum number of transmitted packets before a loss ratio is reported.
      See Also:
    • DEFAULT_LOSSY_MIN_PACKETS

      public static final int DEFAULT_LOSSY_MIN_PACKETS
      Default minimum packet sample size (20 = full 5% bucket resolution).
      See Also:
    • PROP_LOSSY_MODERATE_THRESHOLD

      public static final String PROP_LOSSY_MODERATE_THRESHOLD
      Config property for the decayed loss score above which a peer is de-prioritized in tier selection (soft signal, below the demotion threshold). Default 10%: a peer can be consistently lossy without ever crossing the hard demotion bar, and this keeps such peers out of the preferred picks without evicting them.
      See Also:
    • DEFAULT_LOSSY_MODERATE_THRESHOLD

      private static final float DEFAULT_LOSSY_MODERATE_THRESHOLD
      Default packet loss marking a peer moderately lossy.
      See Also:
    • LOSS_READMIT_MIN_AGE

      private static final long LOSS_READMIT_MIN_AGE
      Minimum time after a loss demotion before a peer may be re-admitted, even with fresh clean evidence. Reduced from 30 min to 10 min to accelerate tier recovery after congestion clears. Asymmetric hysteresis: demotion is instant on weak evidence, re-admission requires both this age and a fresh clean measurement (see shouldReadmitLossy(PeerProfile, long)).
      See Also:
    • LOSS_FAST_TRACK_RATIO

      private static final float LOSS_FAST_TRACK_RATIO
      Fast-track readmission: a fresh loss report below this ratio clears the minimum age even when the tiers are full. Near-spotless evidence is proof enough of recovery. When either tier is under target the fast-track widens to any fresh below-threshold report (see lossyReadmitAllowed(float, long, long, long, long, long, float, float, boolean)): a short tier cannot afford to hold a peer out for a clean-but-not-spotless measurement.
      See Also:
    • LOSSY_SELECTION_PENALTY

      private static final float LOSSY_SELECTION_PENALTY
      Selection penalty for moderately-lossy peers: their random priority range is multiplied by this factor, so they are this many times less likely to be picked than an equal-latency clean peer. Lossiness becomes one of several selection signals instead of a hard gate.
      See Also:
    • DEFAULT_PRIORITY_LATENCY_MS

      private static final float DEFAULT_PRIORITY_LATENCY_MS
      Latency used as a peer's selection weight when it has no measured peer-test average. The widest weight means the widest priority range, so an unmeasured peer is drawn last.
      See Also:
    • AUTO_RTT_MULTIPLIER

      private static final double AUTO_RTT_MULTIPLIER
      Adaptive absolute-RTT ceiling for fast-tier selection. The fast tier is a relative ranking (top N% by speed), so on a degraded network its members can still sit at high absolute latency and intermittently drop build messages. The existing demoteIfHighRTT(Hash, long) uses a fixed bar and only fires on individual peer-test events. These constants define a selection-time ceiling derived from the measured fast-tier RTT boundary (_thresholdRTT): the ceiling is the boundary times AUTO_RTT_MULTIPLIER, clamped to at least AUTO_RTT_FLOOR_MS and at most AUTO_RTT_CAP_MS. Peers whose measured tunnel-test RTT exceeds the ceiling are excluded from fast-tier picks (soft signal, not a tier eviction — consistent with the lossiness-is-one-signal philosophy).
      See Also:
    • AUTO_RTT_FLOOR_MS

      private static final long AUTO_RTT_FLOOR_MS
      Floor: never exclude peers below this RTT, matching the demoteIfHighRTT bar.
      See Also:
    • AUTO_RTT_CAP_MS

      private static final long AUTO_RTT_CAP_MS
      Absolute cap: the ceiling never rises above this even on a fully slow pool.
      See Also:
    • PROP_MAX_PROFILES

      public static final String PROP_MAX_PROFILES
      Config property for the maximum number of peer profiles.
      See Also:
    • _defaultMaxProfiles

      public static volatile int _defaultMaxProfiles
      Runtime-adjustable default max profile count.
    • ABSOLUTE_MAX_PROFILES

      public static final int ABSOLUTE_MAX_PROFILES
      Ceiling on profiles held in memory, in profiles.

      Bounds the RAM store only. What is kept on disk is governed separately by MAX_STORED_PROFILE_FILES, so raising this to let the router use more of its persisted knowledge does not shorten the retention of that knowledge across a restart.

      See Also:
    • MAX_STORED_PROFILE_FILES

      public static final int MAX_STORED_PROFILE_FILES
      Profile files retained on disk before surplus ones are trimmed.

      Deliberately a different number, and a different concern, from ABSOLUTE_MAX_PROFILES: that one bounds profiles held in memory, this one bounds what is kept on disk so it survives a restart. Using one constant for both meant that lowering the disk floor would silently lower the memory ceiling too.

      Below this count no file is deleted unless the peer is genuinely stale, so the floor costs nothing while the store is smaller than it and only starts trimming once the store exceeds it.

      See Also:
    • MIN_MAX_PROFILES

      public static final int MIN_MAX_PROFILES
      Floor for the in-memory profile cap, in profiles.

      Low enough that a small-heap router still runs; getDefaultMaxProfiles() returns this instead of ABSOLUTE_MAX_PROFILES on a slow system.

      See Also:
    • RATES

      private static final long[] RATES
      Rate stat periods for the profile statistics.
    • _reorganizeLock

      private final ReentrantReadWriteLock _reorganizeLock
      Lock guarding the profile maps during reorganization.
    • _lastStarveWarn

      private volatile long _lastStarveWarn
      Timestamp of the last starved-tier warning; selections hold only the read lock, so this is read and written concurrently.
    • MAX_BAD_REPLIES_PER_HOUR

      private static final int MAX_BAD_REPLIES_PER_HOUR
      Bad netdb replies per hour before a peer is penalized.
      See Also:
    • ENOUGH_PROFILES

      private static final int ENOUGH_PROFILES
      Profile count considered enough for full selection.
    • DEFAULT_MIN_CANDIDATE_SAMPLE

      static final int DEFAULT_MIN_CANDIDATE_SAMPLE
      Default floor for candidates examined per selection. @since 0.9.71+.
      See Also:
    • MAX_CANDIDATE_SAMPLE

      static final int MAX_CANDIDATE_SAMPLE
      Upper bound, so a typo cannot turn selection into a full tier scan. @since 0.9.71+.
      See Also:
    • PROP_MIN_CANDIDATE_SAMPLE

      public static final String PROP_MIN_CANDIDATE_SAMPLE
      Property for the floor; see getMinCandidateSample(RouterContext).
      See Also:
    • _minCandidateSample

      private static volatile int _minCandidateSample
      Minimum candidate sample size for threshold math.
    • STARVE_WARN_INTERVAL_MS

      private static final long STARVE_WARN_INTERVAL_MS
      Minimum delay between "tier returned no candidates" warnings. A starved tier is the signal that one gate is rejecting everything, so the warning carries the per-gate counts. It must not flood the log while starvation persists across build passes.
      See Also:
    • _bannedAtSelection

      private final AtomicInteger _bannedAtSelection
      Banned-candidate rejections seen since the last flushSelectionCounters().
    • _cachedMaxRouterInfoAge

      private volatile long _cachedMaxRouterInfoAge
      Cached getMaxRouterInfoAgeMs(), and the timestamp of the fetch. @since 0.9.71+.
    • _cachedMaxRouterInfoAgeTime

      private volatile long _cachedMaxRouterInfoAgeTime
      When _cachedMaxRouterInfoAge was fetched. @since 0.9.71+.
    • ADDRESS_REFRESH_TIMEOUT_MS

      private static final long ADDRESS_REFRESH_TIMEOUT_MS
      Timeout for a RouterInfo lookup issued on behalf of a held peer.
      See Also:
    • _fmt

      private static final DecimalFormat _fmt
      Shared formatter for the statistics display.
    • _cachedBuildSuccess

      private volatile double _cachedBuildSuccess
      Short-lived cache for getTunnelBuildSuccess(). The 6 RateStat lookups are expensive when called per-candidate in peer selection (up to 400 peers × 6 lookups). The ratio is a 10-minute rolling average, so a 15s cache (<2.5% staleness) eliminates ~40x redundant lookups with negligible impact on decision accuracy.
    • _cachedBuildSuccessTime

      private volatile long _cachedBuildSuccessTime
      When the build success ratio was last read.
    • BUILD_SUCCESS_CACHE_MS

      private static final long BUILD_SUCCESS_CACHE_MS
      How long the cached build success ratio stays fresh.
      See Also:
    • NO_HISTORY_ACTIVITY_WINDOW_MS

      private static final long NO_HISTORY_ACTIVITY_WINDOW_MS
      Peers with zero tunnel history are allowed into fast/high-cap tiers if they were heard from within this window. Alive peers that haven't yet built tunnels get a chance. Dead peers are excluded. Under-performers are evicted by existing mechanisms (latency, acceptance ratio, loss probation) within minutes.
      See Also:
  • Constructor Details

    • ProfileOrganizer

      public ProfileOrganizer(RouterContext context)
      Creates a profile organizer with empty tier maps and registers performance tracking statistics for peer classification.
      Parameters:
      context - the router context providing config, stats, and log access
  • Method Details

    • getProfileCount

      public int getProfileCount()
      Profiles held in RAM, as of the last completed reorganisation.
      Returns:
      the in-RAM profile count
    • getQualityCount

      public int getQualityCount()
      Peers passing the quality gates, as of the last completed reorganisation.
      Returns:
      the quality peer count
    • getActiveProfileCount

      public int getActiveProfileCount()
      Profiles with recent activity, as of the last completed reorganisation.

      Computed during the reorganisation walk rather than on demand: countActivePeersInLastHour() iterates every profile while holding _reorganizeLock, which is the lock tunnel peer selection contends on, and CoalesceStatsEvent runs every 50s. Publishing it here costs one comparison per profile in a walk that already holds the lock.

      Returns:
      the recently active profile count
    • isActiveInWindow

      static boolean isActiveInWindow(PeerProfile profile, long hideBefore)
      Whether a profile shows activity within the active window: a peer test counted as active, a successful or failed send, or any contact. Split out so the rule is testable without a reorganise cycle or a live profile set.
      Parameters:
      profile - profile whose send and heard-from timestamps are compared against the cutoff
      hideBefore - cutoff time, i.e. now minus ACTIVE_WINDOW_MS
      Returns:
      true if the profile is recently active
    • isQualityFastPeer

      private static boolean isQualityFastPeer(PeerProfile profile, Map<Hash, PeerProfile> highCap)
      True when a peer in the fast tier has proven throughput or is in the high-cap tier.
      Parameters:
      profile - the fast-tier profile to test
      highCap - the high-capacity tier, for a membership test by peer hash
      Returns:
      true if peak 1m throughput is above 0 KBps or the peer is in highCap
    • putFastPeer

      private void putFastPeer(Hash peer, PeerProfile profile)
      Add a peer to the fast tier, maintaining the quality counter.
      Parameters:
      peer - the peer's hash, the tier map key
      profile - the profile to store for that peer, replacing any earlier entry
    • removeFastPeer

      private PeerProfile removeFastPeer(Hash peer)
      Remove a peer from the fast tier, maintaining the quality counter.
      Parameters:
      peer - the peer's hash, the tier map key
      Returns:
      the profile stored for that peer, or null if it was not in the tier
    • getDefaultMinFastPeers

      public static int getDefaultMinFastPeers()
      Fast tier size the router refills toward.
      Returns:
      the current minimum number of fast-tier peers
    • setDefaultMinFastPeers

      public static void setDefaultMinFastPeers(int val)
      Override the fast tier refill target, clamped to 50-2000 peers.
      Parameters:
      val - requested minimum fast tier size, in peers
    • getDefaultMaxFastPeers

      public static int getDefaultMaxFastPeers()
      Ceiling on the fast tier.
      Returns:
      the current maximum number of fast-tier peers
    • setDefaultMaxFastPeers

      public static void setDefaultMaxFastPeers(int val)
      Override the fast tier ceiling, clamped to 200-3000 peers.
      Parameters:
      val - requested maximum fast tier size, in peers
    • getMinHighCapacityPeers

      public static int getMinHighCapacityPeers()
      High-capacity tier size the router refills toward.
      Returns:
      the current minimum number of high-capacity-tier peers
    • setMinHighCapacityPeers

      public static void setMinHighCapacityPeers(int val)
      Override the high-capacity tier refill target, clamped to 50-3000 peers.
      Parameters:
      val - requested minimum high-capacity tier size, in peers
    • getDefaultMaxHighCapPeers

      public static int getDefaultMaxHighCapPeers()
      Ceiling on the high-capacity tier.
      Returns:
      the current maximum number of high-capacity-tier peers
    • setDefaultMaxHighCapPeers

      public static void setDefaultMaxHighCapPeers(int val)
      Override the high-capacity tier ceiling, clamped to 200-6000 peers.
      Parameters:
      val - requested maximum high-capacity tier size, in peers
    • setLossyThreshold

      public static void setLossyThreshold(float ratio)
      Set the lossy demotion threshold (called by Tuner).
      Parameters:
      ratio - the loss ratio threshold
    • getLossyThreshold

      public static float getLossyThreshold(RouterContext ctx)
      The lossy demotion threshold in effect, tuned value if set else config.
      Parameters:
      ctx - the router context
      Returns:
      the loss ratio threshold
    • getDefaultMaxProfilesValue

      public static int getDefaultMaxProfilesValue()
      Profile count the reorganizer targets by default.
      Returns:
      the current default maximum number of stored profiles
    • setDefaultMaxProfiles

      public static void setDefaultMaxProfiles(int val)
      Override the default profile count, clamped to MIN_MAX_PROFILES-ABSOLUTE_MAX_PROFILES.
      Parameters:
      val - requested maximum profile count
    • getDefaultMaxProfiles

      private static int getDefaultMaxProfiles()
      Returns the default profile count from the config.
      Returns:
      the maximum number of profiles
    • isFirewalled

      private boolean isFirewalled()
      Check if current router is firewalled to adjust peer selection thresholds.
      Returns:
      true if the comm system reports FIREWALLED or REJECT_UNSOLICITED
    • getReadLock

      private void getReadLock()
      Takes the reorganization read lock.
    • tryReadLock

      private boolean tryReadLock()
      Takes the read lock if it is free.
      Returns:
      true if the lock was taken
    • releaseReadLock

      private void releaseReadLock()
      Releases the reorganization read lock.
    • tryWriteLock

      private boolean tryWriteLock()
      Takes the write lock if it is free.
      Returns:
      true if the lock was taken
    • getWriteLock

      private boolean getWriteLock()
      Takes the reorganization write lock, waiting if needed.
      Returns:
      true if the lock was taken
    • releaseWriteLock

      private void releaseWriteLock()
      Releases the reorganization write lock.
    • setUs

      public void setUs(Hash us)
      Store the local router hash for self-exclusion from peer selection.
      Parameters:
      us - our own router hash
    • getUs

      public Hash getUs()
      Self-exclusion hash for this router instance.
      Returns:
      the local router hash, or null if not set
    • getSpeedThreshold

      public double getSpeedThreshold()
      Current minimum speed value for the fast peer tier.
      Returns:
      the speed threshold computed during the last reorganize
    • getFastRTTThreshold

      public double getFastRTTThreshold()
      Average tunnel test time (RTT in ms) of the peer at the fast-tier speed boundary from the last reorganize, or 0.0 if never reorganized.
      Returns:
      the boundary RTT in ms
    • getCapacityThreshold

      public double getCapacityThreshold()
      Current minimum capacity value for the high-capacity peer tier.
      Returns:
      the capacity threshold computed during the last reorganize
    • getIntegrationThreshold

      public double getIntegrationThreshold()
      Current minimum integration value for the well-integrated peer tier.
      Returns:
      the integration threshold computed during the last reorganize
    • getProfile

      public PeerProfile getProfile(Hash peer)
      Retrieve the profile for a peer, or null if unknown. Blocks on the reorganize read lock.
      Parameters:
      peer - the router hash to look up
      Returns:
      the existing profile, or null if none exists
    • getProfileNonblocking

      public PeerProfile getProfileNonblocking(Hash peer)
      Retrieve the profile for a peer without blocking. Returns null immediately if the lock is contended.
      Parameters:
      peer - the router hash to look up
      Returns:
      the existing profile, or null if none exists or lock unavailable
    • getOrCreateProfileNonblocking

      public PeerProfile getOrCreateProfileNonblocking(Hash peer)
      Retrieve an existing profile or create a new one, without blocking on contention. Uses try-lock escalation to avoid blocking the caller.
      Parameters:
      peer - the router hash to look up or create a profile for
      Returns:
      the existing or new profile, or null if the peer is excluded or lock contention
    • addProfile

      public PeerProfile addProfile(PeerProfile profile)
      Insert a fully-constructed profile into the organizer. The profile is added to the not-failing set and high-capacity tier if it meets the current capacity threshold.
      Parameters:
      profile - the profile to add
      Returns:
      the previous profile for the same peer, or null if none existed
    • count

      private int count(Map<Hash, PeerProfile> m)
      Counts the profiles in one pool map.
      Parameters:
      m - the pool map
      Returns:
      the number of profiles
    • countFastPeers

      public int countFastPeers()
      Number of peers classified as fast tier.
      Returns:
      fast peer count (read-lock protected)
    • countHighCapacityPeers

      public int countHighCapacityPeers()
      Number of peers classified as high-capacity tier.
      Returns:
      high-capacity peer count (read-lock protected)
    • countNotFailingPeers

      public int countNotFailingPeers()
      Total number of peer profiles in memory (all tiers).
      Returns:
      profile count (read-lock protected)
    • countActivePeers

      public int countActivePeers()
      Count peers with any send or hear activity in the last 4 hours.
      Returns:
      active peer count (read-lock protected)
    • countActivePeersInLastHour

      public int countActivePeersInLastHour()
      Count peers with activity (send, receive, or failure) within the last hour.
      Returns:
      recently active peer count (read-lock protected)
    • isX

      private boolean isX(Map<Hash, PeerProfile> m, Hash peer)
      Checks whether a peer is in one pool map.
      Parameters:
      m - the pool map
      peer - the peer hash
      Returns:
      true if the peer is in the map
    • isFast

      public boolean isFast(Hash peer)
      Check whether a peer is classified in the fast tier.
      Parameters:
      peer - the router hash to check
      Returns:
      true if the peer is in the fast tier set
    • getFastQualityCount

      public int getFastQualityCount()
      The number of quality peers currently in the fast tier. A quality peer is fast and either high-capacity or has proven throughput. The count gates fast-tier promotion internally: at or above MIN_FAST_QUALITY_COUNT (500) the tier is treated as healthy.
      Returns:
      the count of quality fast peers
    • isHighCapacity

      public boolean isHighCapacity(Hash peer)
      Check whether a peer is classified in the high-capacity tier.
      Parameters:
      peer - the router hash to check
      Returns:
      true if the peer is in the high-capacity tier set
    • isWellIntegrated

      public boolean isWellIntegrated(Hash peer)
      Check whether a peer is classified in the well-integrated tier.
      Parameters:
      peer - the router hash to check
      Returns:
      true if the peer is in the well-integrated tier set
    • clearProfiles

      void clearProfiles()
      Remove all profiles and clear all tier maps. Package-private: only called from tests and router shutdown paths.
    • peerSendsBadReplies

      public boolean peerSendsBadReplies(Hash peer)
      Check whether a peer sends an excessive rate of invalid or failed netDb lookup replies (potential misbehavior indicator).
      Parameters:
      peer - the router hash to check
      Returns:
      true if the peer exceeds the bad-reply threshold in the last hour
    • exportProfile

      public boolean exportProfile(Hash profile, OutputStream out) throws IOException
      Write a peer profile to the given output stream for persistence.
      Parameters:
      profile - the router hash identifying the profile to export
      out - the destination stream for the serialized profile
      Returns:
      true if the profile was found and written, false if no profile exists
      Throws:
      IOException - on write errors
    • remainingToSelect

      static int remainingToSelect(int howMany, Set<Hash> matches)
      How many slots are still free in a cascade's destination set.

      Every tier selector takes howMany as the total the caller wants held in matches when the cascade returns, and every tier — the first one included — asks for only the shortfall this reports. That single convention is what keeps the arithmetic in one place: a call site that pre-subtracts matches.size() itself is a second copy of this computation, and the two copies disagree the moment one of them drifts.

      The failure mode that makes it worth centralising: a caller that sized an ArraySet to exactly howMany has already filled part of it by the time a later tier runs, so a tier that wrote howMany again would overflow it and ArraySet would throw SetFullException — surfaced as pickLowestPriority crashing, which names the symptom rather than the cascade arithmetic behind it.

      Parameters:
      howMany - total peers the caller wants held in matches across the whole cascade
      matches - peers picked so far, by the caller and by the tiers that already ran
      Returns:
      slots still needed, never negative
    • stillSelecting

      static boolean stillSelecting(int howMany, Set<Hash> matches)
      Whether a cascade still has slots left to fill.

      A guard, not a computation the callers should repeat: every tier-selector shortfall branch needs the same remaining > 0 question, and writing it as matches.size() < howMany in ten places is what let one site drift.

      Parameters:
      howMany - peers the caller asked for in total
      matches - peers picked by the tiers that ran before this one
      Returns:
      true if the set still needs more peers
    • selectFastPeers

      public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Select up to howMany fast-tier peers, filling shortfalls from high-capacity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
    • selectFastPeers

      public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet)
      Select fast-tier peers with IP-subnet diversity constraints. Falls through to high-capacity tier on shortfall.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectFastPeers

      public void selectFastPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, SessionKey randomKey, ProfileOrganizer.Slice subTierMode, int mask, MaskedIPSet ipSet)
      Select fast-tier peers deterministically sliced by a random key. This enables different peers per tunnel to improve path diversity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      randomKey - key for deterministic sub-tier assignment
      subTierMode - slicing mode (SLICE_ALL to skip slicing)
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectHighCapacityPeers

      public void selectHighCapacityPeers(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Select high-capacity peers, filling shortfalls from not-failing peers.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
    • selectHighCapacityPeers

      public void selectHighCapacityPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet)
      Select high-capacity peers with IP-subnet diversity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectNotFailingPeers

      public void selectNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Select from not-failing peers, excluding high-capacity tier if requested.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
    • selectNotFailingPeers

      public void selectNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet)
      Select from not-failing peers with IP-subnet diversity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectNotFailingPeers

      public void selectNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing)
      Select from not-failing peers, optionally excluding the high-capacity tier.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
    • selectNotFailingPeers

      public void selectNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet)
      Full-parameter select from not-failing peers.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectNotFailingPeers

      public void selectNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet, boolean preferUnproven)
      Full-parameter select from not-failing peers.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
      preferUnproven - if true, prioritize peers with no tunnel test history so they accumulate profiling data
    • selectActiveNotFailingPeers

      public void selectActiveNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Select from currently connected (established) not-failing peers.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
    • selectActiveNotFailingPeers

      public void selectActiveNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, int mask, MaskedIPSet ipSet)
      Select from currently connected peers with IP-subnet diversity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectTestPeersNonBlocking

      public boolean selectTestPeersNonBlocking(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Non-blocking test peer selection: tries the full fast → high-cap → active chain under a single tryReadLock(). Returns false (leaving matches unchanged) if the read lock cannot be acquired. The caller should skip this round and requeue. Used exclusively by PeerTestJob to avoid blocking on reorganize()'s write lock during startup profiling.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude
      matches - output set populated with selected peer hashes
      Returns:
      true if selection ran, false if lock was not acquired
    • selectHighBandwidthPeers

      public void selectHighBandwidthPeers(int howMany, Set<Hash> exclude, Set<Hash> matches)
      Select up to howMany peers from O/P/X bandwidth tiers (high shared bandwidth) that are not failing. Falls through to selectAllNotFailingPeers on shortfall.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
    • selectHighBandwidthPeers

      public void selectHighBandwidthPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet)
      Select high-bandwidth peers with tier-exclusion and IP-subnet diversity.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets
    • selectHighBandwidthPeers

      private void selectHighBandwidthPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet, double buildSuccess)
      Iterates not-failing peers randomly, filtering for O/P/X bandwidth tiers with reliability checks (acceptance ratio, recent activity, peer tests).
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - mutable set tracking already-selected subnets, or null when mask is 0
      buildSuccess - the build success ratio, fetched once per scan
    • isReliableBandwidthPeer

      static boolean isReliableBandwidthPeer(PeerProfile profile, long now, boolean established)
      Reliability gate for high-bandwidth (O/P/X tier) selection: the peer must show a tunnel acceptance ratio of at least 0.3 AND evidence of recent activity. A successful peer test within 10 minutes, a heard- from/send-success within 30 minutes, or an established commSystem connection. Peers with no profile are not gated (no data yet).

      Pure decision — no context access, safe for unit tests.

      Parameters:
      profile - the peer profile (non-null)
      now - current time in ms
      established - whether commSystem has an established connection
      Returns:
      whether the peer passes the reliability gate
    • selectAllNotFailingPeers

      public void selectAllNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing)
      Select from all not-failing peers randomly, falling through to general peers if the not-failing set is insufficient.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
    • selectAllNotFailingPeers

      public void selectAllNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet)
      Select from all not-failing peers randomly, honouring a /n subnet diversity restriction.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - subnets already represented in this tunnel
    • selectAllNotFailingPeers

      private void selectAllNotFailingPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, boolean onlyNotFailing, int mask, MaskedIPSet ipSet, double buildSuccess, boolean preferUnproven)
      Random iterator over not-failing list with optional tier exclusion. Falls through to selectAllPeers() if shortfall remains.
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      onlyNotFailing - if true, exclude peers already in high-capacity tier
      mask - bitmask length for /n diversity restriction (0 to disable)
      ipSet - subnets already represented in this tunnel, consulted only when mask is non-zero
      buildSuccess - the build success ratio, fetched once per scan and consumed by the selectability gate
      preferUnproven - if true, prioritize peers with no tunnel test history so they accumulate profiling data through exploratory builds
    • selectRemainderFromAllPeers

      private void selectRemainderFromAllPeers(int howMany, Set<Hash> exclude, Set<Hash> matches, double buildSuccess)
      Fallback selection: fill the remaining slots from the union of all peers (fast + high-capacity + not-failing).
      Parameters:
      howMany - target number of peers
      exclude - peers to exclude (may be null)
      matches - output set populated with selected peer hashes
      buildSuccess - the build success ratio, fetched once per scan
    • selectAllPeers

      public Set<Hash> selectAllPeers()
      Gather the union of all peers from fast, high-capacity, and not-failing tiers.
      Returns:
      a new set containing every tracked peer hash
    • selectFastPeers

      public Set<Hash> selectFastPeers()
      Return the current fast tier membership directly. Used by the renderer to avoid iterating all peers for fast-only views.
      Returns:
      unmodifiable snapshot of fast tier hashes
    • selectHighCapacityPeers

      public Set<Hash> selectHighCapacityPeers()
      Return the current high-capacity tier membership directly. Used by the renderer to avoid iterating all peers for highcap-only views.
      Returns:
      unmodifiable snapshot of high-capacity tier hashes
    • selectWellIntegratedPeers

      public Set<Hash> selectWellIntegratedPeers()
      Return the current well-integrated tier membership directly. Used by the renderer to snapshot integration status without per-peer lock acquisitions during rendering.
      Returns:
      unmodifiable snapshot of well-integrated tier hashes
    • getStoredProfileCount

      public int getStoredProfileCount()
      Last known number of profiles stored on disk, from the most recent load, cleanup, or purge in the persistence helper.
      Returns:
      the stored profile count, 0 if the store has never been scanned
    • getEnoughProfiles

      private static int getEnoughProfiles()
      Derives the enough-profiles count from the config.
      Returns:
      the profile count
    • reorganize

      void reorganize()
      Run a standard reorganize round without coalescing. Equivalent to reorganize(false).
    • latencySampleIsBroadEnough

      static boolean latencySampleIsBroadEnough(int measured, int population)
      Fraction of a tier population that must carry a usable first-hop RTT before latency is allowed to influence tier membership.

      A low-latency decision taken over a small measured subset describes that subset, not the tier: the peers we happen to have sessions with are the ones we have been talking to, so an early sample is drawn from whoever was contacted most recently rather than from the population. Requiring a majority of the tier to be measured keeps the decision anchored to the tier it is applied to.

      Parameters:
      measured - peers in the tier with a usable first-hop RTT
      population - peers in the tier
      Returns:
      true when latency may be used to judge this tier
    • sampleFirstHopRtts

      int sampleFirstHopRtts()
      Refresh direct-link RTT for tier peers that already have a transport session, and re-judge their low-latency standing.

      Decoupled from the build pre-connect on purpose. The pre-connect is a one-shot probe fired to force establishment, so it reads the transport at the least informative moment: a session that was just created has not completed a round trip and reports zero. This samples instead from sessions that have been up long enough to have measured, which is the value the latency tiers actually want.

      Peers with no session are skipped rather than probed. Probing here would re-create the problem above: establishing a session to measure it is what makes the measurement unavailable.

      Returns:
      number of peers whose RTT was refreshed
    • feedBaseline

      private void feedBaseline(Map<String, ProtocolRttBaseline> baselines, PeerProfile profile, Collection<Transport> transports)
      Add this peer's best measured latency to its winning transport's baseline.

      The same transport whose value sampleFirstHopRtt(PeerProfile, Collection, long) recorded: the fastest of the peer's sessions. A peer with no readable latency contributes nothing, which is correct - a baseline describes measured links.

      Parameters:
      baselines - per-style accumulators for this cycle
      profile - the sampled peer
      transports - live transports to query
    • publishBaselines

      private void publishBaselines(Map<String, ProtocolRttBaseline> baselines)
      Publish this cycle's baselines and report them as stats.

      The stat is what makes the calibration observable rather than assumed: a baseline of 0 means the transport has not yet gathered enough samples, and the low-latency judgement is running on the absolute floor unchanged. Watching the sample count alongside the value is the check that a transport is actually supplying latency.

      Parameters:
      baselines - per-style accumulators built this cycle
    • getCalibratedLatencyFloorMs

      long getCalibratedLatencyFloorMs(String style)
      The calibrated latency floor for a transport style, or -1 when that transport has not gathered enough samples to have an opinion.

      Callers must treat -1 as "no calibrated opinion" and fall back to the absolute rule, never as "this peer qualifies".

      Parameters:
      style - the transport style, e.g. "NTCP2" or "SSU2"
      Returns:
      the calibrated floor in ms, or -1 if unknown
    • getCalibratedLatencyFloorMs

      long getCalibratedLatencyFloorMs(Hash peer)
      The calibrated latency floor for a peer, choosing the style of the transport that measured it.
      Parameters:
      peer - the peer to look up
      Returns:
      the calibrated floor in ms, or -1 if unknown
    • publishLatencyCoverage

      private void publishLatencyCoverage()
      Report how much of each tier holds a current first-hop RTT.

      This is the question the other stats cannot answer. Tier size says how many peers exist, and the baseline stats say how many samples fed the floor, but neither says how many peers hold a reading that is still inside the validity window - a tier can be fully counted and largely unmeasured while looking identical to a healthy one from outside.

      Reported as a count against nothing rather than a ratio, because a ratio computed over a shrinking tier flatters itself.

    • applyBaselineMultiple

      private long applyBaselineMultiple(long baselineMs)
      Scales a baseline timeout by the configured multiple.
      Parameters:
      baselineMs - the baseline timeout
      Returns:
      the adjusted timeout
    • applyBaselineMultiple

      static long applyBaselineMultiple(long baselineMs, int multiple)
      Scale a calibrated baseline into the latency a peer may sit under and still count as low latency.

      Pure arithmetic, extracted so the overflow and sentinel rules can be exercised without a router context.

      Parameters:
      baselineMs - the calibrated baseline in ms
      multiple - how many times it may be exceeded; values of 1 or less return the baseline unscaled
      Returns:
      the qualifying threshold in ms, or baselineMs unchanged when it carries no opinion
    • getCalibratedLatencyThresholdMs

      long getCalibratedLatencyThresholdMs(Hash peer)
      The qualifying latency threshold for a peer, from the calibrated baseline of the transport that measured it.

      This is what the profile actually compares against - the baseline scaled by PROP_LOW_LATENCY_MULTIPLE - as opposed to getCalibratedLatencyFloorMs(Hash), which returns the unscaled baseline that peer.latencyBaseline.* reports.

      Parameters:
      peer - the peer to look up
      Returns:
      the qualifying threshold in ms, or -1 if unknown
    • getCohortLatencyThresholdMs

      long getCohortLatencyThresholdMs()
      The qualifying latency threshold for the fast cohort, from the cohort baseline of its fastest members.

      Scaled on the same terms as getCalibratedLatencyThresholdMs(Hash) so the two bounds stay comparable; a cohort mean and a transport baseline are the same kind of quantity and neither is a cutoff on its own.

      Returns:
      the qualifying threshold in ms, or a non-positive value when the cohort has too few measured peers to hold an opinion
    • sampleTierPeers

      private List<PeerProfile> sampleTierPeers()
      Bounded random sample of the retained peers for RTT measurement, capped at DEFAULT_MIN_CANDIDATE_SAMPLE with a rotated start so successive cycles do not always read the same prefix.

      Sampling is safe here because a peer's RTT is a property of its transport session, not an accumulator: a peer missed this cycle is measured in a later one.

      Covers every tier the Organizer retains, not just the two the fast tier is drawn from. A profile outside the fast and high-capacity tiers can still hold the only current measurement of a peer that later qualifies, and reading the tiers it can currently be selected from would leave its latency unmeasured until something promoted it - at which point the first hop is chosen on no evidence at all. There is no larger population to read: profiles are retained only in these maps, so this is the whole of what exists.

      Returns:
      the sampled profiles, at most sampleFirstHopRttLimit(int)
    • sampleFirstHopRttLimit

      static int sampleFirstHopRttLimit(int total)
      How many tier peers to sample for first-hop RTT in one reorganize.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      total - combined size of the two tiers
      Returns:
      the sample size, never negative
    • sampleFirstHopRtt

      private int sampleFirstHopRtt(PeerProfile profile, Collection<Transport> transports, long now)
      Record the best RTT any transport reports for this peer, if any.
      Parameters:
      profile - the peer to sample; nothing is recorded when it is null
      transports - the peer's transports, each asked for its estimated RTT — the largest reported value wins
      now - router time in ms, stamped onto the sample when the winning transport reports no measurement time
      Returns:
      the recorded RTT in ms, or 0 if there was no measurement
    • fastOrHighCapCount

      int fastOrHighCapCount()
      Peers in either the fast or the high-capacity tier, counting each peer once.

      A peer can be both, so neither tier contains the other and adding the two counts over-reports. This is the number the profile-tier graph needs for its high-capacity line to be a true subset of the fast line, rather than merely a smaller number that happens to be plotted beside it.

      Walks the smaller tier and probes the larger, so the cost is O(min(fast, highCap)) once per coalesce rather than a full merge.

      Returns:
      size of the union of the two tiers
    • getFastOrHighCapCount

      public int getFastOrHighCapCount()
      Live size of the fast or high-capacity union, a peer in both tiers counted once. Read-locked, so it is safe to call outside a coalesce pass.

      Callers that already hold the write lock — reorganize(). Must call the package-visible fastOrHighCapCount() directly; going through this accessor would re-enter the lock.

      Returns:
      size of the union of the two tiers
      See Also:
    • reorganize

      void reorganize(boolean shouldCoalesce)
      Reorganizes peer profiles into performance-based tiers (fast, high-capacity, etc.) and expires stale entries.

      This method:

      • Coalesces stats if requested and uptime conditions are met. Peak throughput values are never decayed.
      • Filters out unreachable, inactive, or low-tier peers.
      • Recalculates dynamic thresholds for speed, capacity, and integration.
      • Rebuilds internal tier maps and the global profile ordering.
      • Expires profiles that haven't been active recently to bound memory usage.

      Memory Safety: To prevent unbounded memory growth (e.g., OOM after 8+ hours), this method ensures that expired profiles are removed from all data structures—even if the full reorganization is skipped due to lock contention. A best-effort expiration pass runs outside the write lock to mitigate leaks during high contention.

      Parameters:
      shouldCoalesce - if true, coalesce statistics for active profiles
    • isExpiredProfile

      static boolean isExpiredProfile(PeerProfile profile, long now, long expireActive, long expirePassive, long expireGossip, long expireUntracked)
      Whether a profile has expired from its activity tier and should be purged: no activity (send/heard/heard-about) within the tier's expiration window. Tiered expiration: Active > Passive > Gossip; profiles with no tunnel history are purged on the tighter untracked window regardless of tier. A profile with zero timestamps sits in the gossip tier.

      Pure decision. No context access, safe for unit tests.

      Parameters:
      profile - profile to test; its expiration window is picked from its tier, the tier of the newest activity timestamp it carries
      now - current time in ms
      expireActive - active-tier window in ms
      expirePassive - passive-tier window in ms
      expireGossip - gossip-tier window in ms
      expireUntracked - untracked window in ms
      Returns:
      whether the profile is expired
    • buildCandidateProfiles

      private ProfileOrganizer.CandidateSet buildCandidateProfiles(long now, long expireActive, long expirePassive, long expireGossip, long expireUntracked)
      Builds the candidate profile set for this reorganize round, filtering expired, unreachable, and excluded profiles.
      Parameters:
      now - current time in ms, the reference point for the windows below
      expireActive - active-tier window in ms
      expirePassive - passive-tier window in ms
      expireGossip - gossip-tier window in ms
      expireUntracked - untracked window in ms, the tighter cap applied to profiles with no tunnel history
      Returns:
      the surviving profiles, with the integration total and the expired and kept counts
    • lockedRebuildTiers

      private void lockedRebuildTiers(Set<PeerProfile> candidates, double buildSuccess)
      Reinserts all active profiles into the tier maps. Must be called with the write lock held.

      Also opens the promotion-deferral cycle, which the tier rebuild is the only place that can judge: a promotion is the moment a stale RouterInfo starts costing something, and this loop is the sole promotion path that sees the whole profile population at once.

      Parameters:
      candidates - the surviving profile set
      buildSuccess - cached tunnel build success ratio
    • passesTierGates

      boolean passesTierGates(PeerProfile profile, double buildSuccess, long now, boolean requireAcceptance)
      Common gate for fast/high-cap tier fill passes: the peer must be selectable, have acceptable tunnel acceptance, no recent tunnel failures, and not be in loss probation. Evaluation order preserves the original short-circuit chain (cheap pure checks before the netDb-backed selectability check is already handled by callers that test other cheap conditions first).

      No side effects. Safe to evaluate without holding locks.

      Tunnel acceptance is required by the fast-tier passes and not by the high-capacity pass. The two tiers do different jobs: high capacity is the reservoir the fast tier is seeded from, so gating it on acceptance leaves no deep supply to draw on — when a fast peer demotes, the peers beside it in high capacity have already been filtered by the same criterion. High capacity therefore admits any provably alive peer of the right bandwidth class, and acceptance decides fast-tier membership only. A poor accepter is still evicted from high capacity, so the reservoir turns over rather than accumulating them.

      Parameters:
      profile - the candidate profile
      buildSuccess - the build success ratio in [0.0, 1.0]
      now - current time in ms
      requireAcceptance - whether the peer must also have acceptable tunnel acceptance; false admits by capacity and liveness alone
      Returns:
      whether the peer passes the tier gates
    • passesTierGates

      boolean passesTierGates(PeerProfile profile, double buildSuccess, long now)
      Common gate for the fast-tier fill passes, which always require acceptable tunnel acceptance.
      Parameters:
      profile - the candidate profile
      buildSuccess - the build success ratio in [0.0, 1.0]
      now - current time in ms
      Returns:
      whether the peer passes the fast-tier gates
    • hasRecentTierActivity

      static boolean hasRecentTierActivity(PeerProfile profile, long activeCutoff)
      True if the peer showed recent activity within the given cutoff: a heard-from or successful send at or after the cutoff. Used to keep stale peers out of the fast/high-cap tiers.

      Pure decision. No context access, safe for unit tests.

      Parameters:
      profile - profile whose heard-from and last-successful-send timestamps are checked
      activeCutoff - earliest allowed activity timestamp in ms
      Returns:
      whether the peer has recent activity
    • computeFastRttCeiling

      static long computeFastRttCeiling(double boundaryRttMs)
      Compute the adaptive absolute-RTT ceiling for fast-tier selection.

      The ceiling is the measured fast-tier RTT boundary times a multiplier, clamped to at least AUTO_RTT_FLOOR_MS and at most AUTO_RTT_CAP_MS. Flooring keeps the bar at least as tight as the existing fixed high-RTT demotion; capping prevents a uniformly-slow pool from raising the ceiling without bound, so clearly-slow outliers are still excluded. Pure decision — no context access, safe for unit tests.

      Parameters:
      boundaryRttMs - the fast-tier RTT boundary from the last reorganize (_thresholdRTT); its own scaling amplifies the typical pool latency so the ceiling tracks the network
      Returns:
      the selection ceiling in ms; never below AUTO_RTT_FLOOR_MS and never above AUTO_RTT_CAP_MS. A boundary at or below 0 (no measurement yet) returns the floor so nothing is over-trimmed.
    • computeAdaptiveRttCeiling

      long computeAdaptiveRttCeiling(double boundaryRttMs, double buildSuccess)
      Compute the adaptive absolute-RTT ceiling for fast-tier selection, floored at 2× the startup baseline when build success is degraded. Prevents RTT ceiling amplification during cascade failure: when the entire network slows, the ceiling rises with it and peers that would cause failures are allowed through. The baseline floor keeps the ceiling tight during degradation while allowing natural growth when builds are healthy.
      Parameters:
      boundaryRttMs - the fast-tier RTT boundary from the last reorganize
      buildSuccess - current tunnel build success ratio [0.0, 1.0]
      Returns:
      the selection ceiling in ms
    • aboveRttCeiling

      static boolean aboveRttCeiling(PeerProfile profile, long rttCeilingMs)
      True if the peer's measured tunnel-test RTT exceeds the adaptive selection ceiling, indicating it should be excluded from this fast-tier pick while still remaining in the tier (soft signal. The ceiling applies at selection time, not tier membership). A zero/unknown RTT never trips the ceiling.
      Parameters:
      profile - the candidate profile
      rttCeilingMs - the adaptive ceiling from computeFastRttCeiling(double)
      Returns:
      whether the peer is above the ceiling
    • isEligibleForTierFill

      private boolean isEligibleForTierFill(PeerProfile profile, double buildSuccess, long now, long activeCutoff)
      Full eligibility gate for filling the fast/high-cap tiers from the active profile set: not ourselves, passes the tier gates, and active within the cutoff window.
      Parameters:
      profile - the candidate profile
      buildSuccess - the build success ratio in [0.0, 1.0]
      now - current time in ms
      activeCutoff - earliest allowed activity timestamp in ms
      Returns:
      whether the peer may be added to the tier
    • isEligibleForTierFill

      private boolean isEligibleForTierFill(PeerProfile profile, double buildSuccess, long now, long activeCutoff, boolean requireAcceptance)
      Whether a peer may be admitted to a tier by a fill pass.
      Parameters:
      profile - the candidate profile
      buildSuccess - the build success ratio in [0.0, 1.0]
      now - current time in ms
      activeCutoff - peers without activity at or after this are skipped
      requireAcceptance - whether acceptable tunnel acceptance is required; false for the high-capacity reservoir, which admits by capacity and liveness alone
      Returns:
      whether the peer may be admitted
    • fillFastTierFallbacks

      private int fillFastTierFallbacks(long now, Set<PeerProfile> activeProfiles, double buildSuccess)
      Fills the fast tier to the minimum via up to three fallback passes. Must be called with the write lock held. Returns the number added.
      Parameters:
      now - current time in ms, the reference for the tier gates and the activity cutoff of the last pass
      activeProfiles - profiles surviving this reorganize round; the candidate pool when the high-capacity tier is empty
      buildSuccess - the build success ratio in [0.0, 1.0], fetched once per cycle
      Returns:
      how many profiles were added to the fast tier, 0 if it already met its minimum
    • fillHighCapFallback

      private int fillHighCapFallback(long now, Set<PeerProfile> activeProfiles, double buildSuccess)
      Fills the high-capacity tier to the minimum with recently-active selectable peers. Must be called with the write lock held. Returns the number added.
      Parameters:
      now - current time in ms, the reference for the activity cutoff
      activeProfiles - profiles surviving this reorganize round, scanned in iteration order until the minimum is reached
      buildSuccess - the build success ratio in [0.0, 1.0], also used to size the activity window during normal operation
      Returns:
      how many profiles were added to the high-capacity tier, 0 if it already met its minimum
    • purgeUnusableFromTiers

      private void purgeUnusableFromTiers(long now, double buildSuccess, int profileCount)
      Evicts peers that stopped qualifying since the tiers were last rebuilt.

      This is a within-window catch, not the mechanism that turns the tiers over. The rebuild at step 4 clears both tiers and repopulates them through lockedPromoteProfileToTiers(PeerProfile, double), which applies acceptance, congestion, loss-probation and excessive-failure gates to every peer it admits. A peer that is already disqualified therefore never survives the rebuild, and the only thing left for this pass to remove is a peer that qualified at rebuild time and stopped qualifying during the reorg window that followed.

      The consequence for anyone reading the stats: with the rebuild doing the filtering, every reason counts near zero in steady state, and that is the expected reading rather than evidence that a gate is inert. An all-zero peer.tierEvicted.* series over many cycles means "nothing degraded mid-window", not "eviction is disabled". The gates this pass duplicates are still load-bearing, but they live in the promotion path.

      Two reasons have no promotion-path counterpart and so are this pass's own: ProfileOrganizer.PurgeReason.UNREACHABLE, which tests a momentary transport flag that promotion never reads, and ProfileOrganizer.PurgeReason.BANNED, which promotion covers only through passesBasicGates(Hash). Those two are the ones to watch when diagnosing.

      Must be called with the write lock held.

      Parameters:
      now - current time in ms
      buildSuccess - the build success ratio, which relaxes the acceptance threshold under stress
      profileCount - this cycle's profile count, which sets the soft-purge threshold
    • publishPurgeReasonStats

      private void publishPurgeReasonStats(Map<ProfileOrganizer.PurgeReason, Integer> reasons, boolean fastSoft, boolean highCapSoft, int threshold)
      Publish per-reason eviction counts for this reorganize cycle.

      Registered at the point of use, as with the other tier stats: StatManager drops a name it has never been told about, so a name registered only in the constructor would publish nothing at all. All five are registered every cycle whether or not that reason fired, so a zero can be told apart from a stat that is not reporting.

      Parameters:
      reasons - counts accumulated by both tier passes, keyed by reason
      fastSoft - whether the fast tier was above the soft-purge threshold
      highCapSoft - whether the high-capacity tier was above it
      threshold - the threshold in force, so a sudden stop is explicable
    • count

      private static int count(Map<ProfileOrganizer.PurgeReason, Integer> reasons, ProfileOrganizer.PurgeReason reason)
      Count of one eviction reason in this cycle's tally.
      Parameters:
      reasons - this cycle's eviction counts, keyed by purge reason
      reason - the reason whose count is wanted
      Returns:
      the count, zero if that reason never fired
    • softPurgeThreshold

      static int softPurgeThreshold(int knownProfiles)
      The smallest tier size at which peers may be evicted for a soft reason.

      Scaled to the number of known profiles rather than fixed, because a fixed count means different things at different network sizes: demanding 500 peers of a 5,000-profile network is a real constraint, while demanding the same of an 800-profile network is simply unreachable, which silently disables the eviction entirely and lets the tier decay one-way.

      One tenth of the known profiles, capped at MAX_SOFT_PURGE_FLOOR: the fraction keeps the requirement proportionate on small networks, and the cap stops it becoming so large on a big one that no tier ever qualifies. Below roughly MAX_SOFT_PURGE_FLOOR * 10 profiles the cap is not reached, so a small router is held to the proportionate bar.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      knownProfiles - the number of profiles the router knows about
      Returns:
      the minimum tier size at which soft eviction applies; no size qualifies when the population is unknown
    • purgeReasonFor

      static ProfileOrganizer.PurgeReason purgeReasonFor(boolean banned, boolean unreachable, boolean tierCapable, boolean softEvictionAllowed, boolean hasRecentFailures, boolean inLossProbation, boolean lowAcceptance)
      Why a peer listed in a tier should be evicted, or null to keep it.

      Extracted from purgeUnusableFromMap(Map, String, long, double, boolean, Map) so the precedence and the gating are testable without a router context. The order is a precedence, not a set of independent checks: a banned peer is reported as banned even if it is also unreachable, because that is the most actionable fact about it and the one an operator greps for.

      The three categorical reasons apply at any tier size. The two soft ones require softEvictionAllowed, which the caller derives from the population-scaled threshold, so a starved tier keeps peers that are merely underperforming while still losing the ones that must not be there at all.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      banned - the peer is banlisted
      unreachable - the transport reports the peer unreachable
      tierCapable - the peer still meets its tier's bandwidth class
      softEvictionAllowed - whether the tier is above the soft-purge threshold
      hasRecentFailures - the peer has sustained recent build/test failures
      inLossProbation - the peer is inside its loss-probation window
      lowAcceptance - the peer's tunnel acceptance is below the bar
      Returns:
      the reason to evict for, or null to leave the peer in place
    • purgeUnusableFromMap

      private void purgeUnusableFromMap(Map<Hash, PeerProfile> tier, String tierName, long now, double buildSuccess, boolean allowSoftEviction, Map<ProfileOrganizer.PurgeReason, Integer> reasons)
      Evicts every peer in one tier map that purgeReasonFor(boolean, boolean, boolean, boolean, boolean, boolean, boolean) rejects.

      Acceptance is an eviction criterion for both tiers, even where high-capacity admission ignores it. A peer that declines too many of our build requests is wasted work whichever tier lists it, and leaving such a peer listed in the fast tier in particular means it keeps being offered as a first hop. Admission is the permissive side of that trade; this is what stops the permissiveness from accumulating poor accepters.

      Only PurgeReason.BANNED and PurgeReason.UNREACHABLE can be first to fire here for a peer the rebuild had already admitted; the other three are re-checks of gates the rebuild applied. See purgeUnusableFromTiers(long, double, int) for why that makes the steady-state counts near zero.

      Parameters:
      tier - the tier map to purge, mutated in place
      tierName - "fast" or "high-cap" for the log message
      now - current time in ms
      buildSuccess - the build success ratio in [0.0, 1.0], which relaxes the acceptance threshold under stress so the tiers cannot be emptied by a network-wide event
      allowSoftEviction - whether this tier is above softPurgeThreshold(int), gating the soft reasons
      reasons - tally shared with the other tier's pass, accumulated in place
    • needsTierRestore

      static boolean needsTierRestore(int currentSize, int oldSize)
      Whether a shrunken tier needs restoration: below half its old size and the old size was substantial. Prevents starvation when thresholds shift unfavorably.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      currentSize - current tier size
      oldSize - pre-reorganize tier size
      Returns:
      whether the tier should be restored
    • isRestorableTierPeer

      private boolean isRestorableTierPeer(Hash peer, PeerProfile profile, Map<Hash, PeerProfile> tier, double buildSuccess, long now, boolean highCap)
      Whether a preserved pre-reorganize tier entry may be re-added: not already present in the tier, selectable, no recent tunnel failures, not in loss probation (fast tier) or below the loss demotion threshold (high-cap tier), and acceptable tunnel acceptance. Evaluation order matches the original restore loops.
      Parameters:
      peer - the peer hash
      profile - the preserved profile
      tier - the tier map being restored into
      buildSuccess - the build success ratio in [0.0, 1.0]
      now - current time in ms
      highCap - whether restoring the high-cap tier (loss-demotion gate instead of loss probation)
      Returns:
      whether the peer may be restored
    • restorePreservedPeers

      private void restorePreservedPeers(long now, Set<Hash> oldFastPeers, Set<Hash> oldHighCapPeers, double buildSuccess)
      Re-adds preserved pre-reorganize tier entries when the rebuild shrank the tiers too much. Must be called with the write lock held.
      Parameters:
      now - current time in ms, the reference for the loss and acceptance checks
      oldFastPeers - the fast tier's peer hashes as they were before the rebuild
      oldHighCapPeers - the high-capacity tier's hashes as they were before the rebuild
      buildSuccess - the build success ratio in [0.0, 1.0], used by the selectability and acceptance gates
    • countActiveProfiles

      private int countActiveProfiles(long now)
      Counts profiles with recent activity, over the same set as countActivePeersInLastHour() so the published gauge and the on-demand scan agree. Called during the reorganise, which already holds the write lock.
      Parameters:
      now - current time in ms; activity is anything at or after now minus ACTIVE_WINDOW_MS
      Returns:
      how many not-failing profiles are active inside the window
    • countQualityPeers

      private int countQualityPeers(long now, double buildSuccess)
      Counts quality peers (fast/high-cap with good acceptance plus recent activity).
      Parameters:
      now - current time in ms; the activity cutoff is 24 hours before it
      buildSuccess - the build success ratio in [0.0, 1.0], which scales the acceptance requirement
      Returns:
      how many distinct tier peers pass the quality test, each peer counted once
    • purgeStaleProfileFiles

      public void purgeStaleProfileFiles()
      Remove profile files that are no longer worth keeping: those whose peer is genuinely stale at any size, and those beyond the disk retention floor.

      See ProfilePersistenceHelper.purgeExcessProfiles(Set, int) for the two rules and why neither lets a recently active peer be deleted for its bandwidth tier.

    • calculateCapacityThresholdFromSet

      private double calculateCapacityThresholdFromSet(Set<PeerProfile> activeProfiles, int totalPeers)
      Helper to calculate capacity threshold from a pre-filtered set of active profiles. Avoids re-iterating the full set multiple times.
      Parameters:
      activeProfiles - the pre-filtered profiles whose capacity values are sampled
      totalPeers - the size of that set, used as the mean divisor and as the highest index read; 0 skips the sampling entirely
      Returns:
      the capacity threshold, CapacityCalculator.GROWTH_FACTOR when there are no peers to sample
    • countExceedingMean

      static int countExceedingMean(double[] capacities, double meanCapacity)
      How many of the given capacities are strictly above the mean.

      Scanned rather than derived from an index into the sorted array: the mean is a repeated value whenever capacities cluster, and Arrays.binarySearch returns only some index among equal entries, so an index-derived count silently includes peers whose capacity merely equals the mean and can flip the >= minHighCap branch. The scan is over a primitive array the sort above already walked.

      Parameters:
      capacities - the capacities; the caller holds them sorted, but the count does not depend on their order
      meanCapacity - the mean to compare against
      Returns:
      the number of entries greater than the mean, never negative
    • enforceProfileCap

      private void enforceProfileCap()
      Enforces the global profile cap by evicting the least valuable peers when over capacity.

      Eviction priority (from most to least likely to be kept):

      1. Peers in _fastPeers (fast + reliable)
      2. Peers in _highCapacityPeers
      3. Peers with recent activity (last 48 hours)
      4. Peers with higher capacity value

      This method assumes the write lock is held.

    • clampMaxProfiles

      static int clampMaxProfiles(int maxProfiles)
      Clamps the configured max-profiles value into [100, ABSOLUTE_MAX_PROFILES].

      Pure decision — no context access, safe for unit tests.

      Parameters:
      maxProfiles - the configured or default value
      Returns:
      the clamped value
    • isEvictable

      static boolean isEvictable(PeerProfile profile, Map<Hash, PeerProfile> fastPeers, Map<Hash, PeerProfile> highCapPeers, long activeThreshold, int fastPeerLimit, int highCapacityLimit)
      Whether a profile is eligible for eviction under the profile cap: not protected by a fast/high-cap tier slot that still has room below its limit, and no send or heard-from activity within the active window.

      Pure decision. No context access, safe for unit tests.

      Parameters:
      profile - the profile under consideration
      fastPeers - the current fast tier map
      highCapPeers - the current high-cap tier map
      activeThreshold - the activity window cutoff (now - 48h)
      fastPeerLimit - fast tier size at or below which its members keep eviction protection; above it, fast members are evictable like any other
      highCapacityLimit - high-cap tier size at or below which its members keep eviction protection; above it, high-cap members are evictable
      Returns:
      whether the profile may be evicted
    • isStaleAbsentPeer

      static boolean isStaleAbsentPeer(PeerProfile profile, long now, long absentThreshold)
      Whether the peer's last activity (send, heard-from, or heard-about) is older than the absent threshold. I.e. it has been absent from the netDb for at least that long. A peer that was never contacted (all-zero timestamps) counts as stale.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      profile - profile whose newest send, heard-from or heard-about timestamp sets the age
      now - current time in ms
      absentThreshold - stale threshold in ms
      Returns:
      whether the peer is stale-absent
    • evictProfilesNotInNetdb

      void evictProfilesNotInNetdb()
      Evict profiles for peers no longer in the network database. Keeps profile files on disk — they'll be reloaded if the peer reappears. Acquires the write lock.
    • calculateCapacityThreshold

      private double calculateCapacityThreshold(double meanCapacity, int numExceedingMean, int totalPeers, double thresholdAtMedian, double thresholdAtMinHighCap, double thresholdAtLowest)
      Interpolates the capacity threshold for selection.
      Parameters:
      meanCapacity - the mean capacity of the peers
      numExceedingMean - peers above the mean
      totalPeers - the peers considered
      thresholdAtMedian - the threshold when half are above
      thresholdAtMinHighCap - the threshold at the minimum high cap
      thresholdAtLowest - the threshold at the lowest point
      Returns:
      the capacity threshold
    • calculateSpeedThreshold

      private double calculateSpeedThreshold(Set<PeerProfile> reordered, long now, double capacityThreshold)
      Computes the speed threshold over the high-capacity peers.
      Parameters:
      reordered - profiles ordered by capacity
      now - the current time
      capacityThreshold - the capacity threshold in effect
      Returns:
      the speed threshold
    • speedThresholdCutoff

      static int speedThresholdCutoff(int qualifyingCount)
      Rank boundary for the speed threshold: the top 30% of qualifying peers, capped at 50. 0-based, so rank 0 is the fastest.

      The qualifying count drives the rank, never the profile count: the profile count is an order of magnitude larger and would pin the rank at the cap, asking for a peer that does not exist.

      Pure decision. No context access, safe for unit tests.

      Parameters:
      qualifyingCount - number of peers that passed the capacity and activity gates
      Returns:
      the rank to read the boundary peer from, or 0 for an empty set
    • selectSpeedBoundary

      static PeerProfile selectSpeedBoundary(Collection<PeerProfile> profiles, long now, double capacityThreshold)
      The profile whose speed becomes the speed threshold: the one at rank speedThresholdCutoff(int) of the qualifying set, ranked fastest first. Only that one profile is ever read, and the rank is capped at 50, so a bounded top-K selection replaces a full sort of every profile.
      Parameters:
      profiles - candidates, not necessarily sorted
      now - current time in ms, for the activity test
      capacityThreshold - minimum capacity to qualify
      Returns:
      the boundary profile, or null if none qualifies
    • qualifiesForSpeedRank

      private static boolean qualifiesForSpeedRank(PeerProfile profile, long now, double capacityThreshold)
      Whether a profile qualifies for the speed-threshold ranking: enough capacity to count and recently active. One definition, shared by the qualifying count and the bounded selection so the two passes cannot disagree about who is in the set.
      Parameters:
      profile - the profile to test
      now - current time in ms, for the activity test
      capacityThreshold - minimum capacity to qualify
      Returns:
      true if the profile takes part in the speed ranking
    • countQualifying

      static int countQualifying(Collection<PeerProfile> profiles, long now, double capacityThreshold)
      How many of the given profiles qualify for the speed-threshold ranking. The rank read from the ranked set is a fraction of this count, not of the profile count, so it has to be known before the boundary is selected.
      Parameters:
      profiles - candidates, not necessarily sorted
      now - current time in ms, for the activity test
      capacityThreshold - minimum capacity to qualify
      Returns:
      the qualifying count, never negative
    • selectNthFastest

      static PeerProfile selectNthFastest(Collection<PeerProfile> profiles, long now, double capacityThreshold, int k)
      Select the k-th fastest qualifying profile (1-based) without sorting the whole candidate set.

      Holds the k fastest profiles seen so far in an unsorted bounded array plus the index of the slowest: a slower profile is discarded in O(1), one that displaces the slowest costs a k-element rescan. Worst case O(n·k), and k is at most 51 for the only caller.

      Ties in speed may resolve to a different peer of equal speed than a full sort would have picked; only the boundary value is read.

      Parameters:
      profiles - candidates, not necessarily sorted
      now - current time in ms, for the activity test
      capacityThreshold - minimum capacity to qualify
      k - how many of the fastest to keep, 1-based
      Returns:
      the k-th fastest qualifying profile, or null if fewer than k qualify
    • lookupRouterInfoUnvalidated

      private RouterInfo lookupRouterInfoUnvalidated(Hash peer)
      Fetch a peer's local RouterInfo without re-running the network database's validate().

      Every caller here reads raw RouterInfo content (tier, capabilities, addresses) and applies its own gate, so nothing depends on validation rejecting an entry that is present. It already ran when the entry was stored. Callers must not use this to evict: the validating lookup's failure path calls fail(key), which is the netDb's job.

      Falls back to lookupRouterInfoLocally on a miss, needed only for DummyNetworkDatabaseFacade: it serves lookupRouterInfoLocally from its own map and returns null from lookupLocallyWithoutValidation.

      Parameters:
      peer - the peer hash
      Returns:
      the local RouterInfo, or null if absent or not a RouterInfo
    • lookupRouterInfoUnvalidated

      static RouterInfo lookupRouterInfoUnvalidated(RouterContext ctx, Hash peer)
      lookupRouterInfoUnvalidated(Hash) with the context passed in, so the fingerprint builder below is reachable from tests.
      Parameters:
      ctx - the context holding the NetDb to look the peer up in
      peer - the peer hash
      Returns:
      the local RouterInfo, or null if absent or not a RouterInfo
    • isExcludedFromProfiling

      boolean isExcludedFromProfiling(Hash peer)
      Check if a peer should be excluded from profiling. Excludes low bandwidth tiers (K, L, M, Unknown) and G cap (no tunnels).

      This is the single gate in front of profile creation, and it covers all three ways one can be made: getOrCreateProfileNonblocking(Hash), addProfile(PeerProfile) (which is how stored profiles are loaded at startup), and the periodic sweep. A peer failing it therefore has no profile at all, so it costs no RAM and cannot be written to disk.

      The bandwidth half of this check was documented here but never performed: the body tested only the no-tunnels capability, while the tier test sat in isLowBandwidthTierName(String) with no caller in the codebase. Peers advertising K, L, M or Unknown were profiled, tracked and persisted despite advertising that they will not usefully host a tunnel.

      Parameters:
      peer - the peer hash
      Returns:
      true if the peer should not be profiled
    • isExcludedFromProfiling

      static boolean isExcludedFromProfiling(String capabilities, String bandwidthTier)
      Should a peer advertising these capabilities and bandwidth tier be profiled?

      Pure decision form of isExcludedFromProfiling(Hash), so the rule can be pinned without a RouterInfo or a network database.

      Parameters:
      capabilities - the peer's advertised capabilities, never null
      bandwidthTier - the peer's advertised bandwidth tier
      Returns:
      true if no profile should be created for the peer
    • isLowBandwidthTierName

      static boolean isLowBandwidthTierName(String tier)
      Whether a bandwidth tier name is one of the tiers excluded from profiling. Pure decision — no context access, safe for unit tests.
      Parameters:
      tier - the advertised bandwidth tier
      Returns:
      true for K, L, M, or Unknown
    • isCongestedPeer

      private boolean isCongestedPeer(Hash peer)
      Check if a peer has a congestion capability cap (D or E).
      Parameters:
      peer - the peer whose advertised capabilities are inspected; a peer with no RouterInfo in the database is not treated as congested
      Returns:
      true if the peer is moderately or severely congested
    • isHighBandwidthCapable

      private boolean isHighBandwidthCapable(Hash peer)
      Whether the peer qualifies for the high-capacity tier based on advertised bandwidth tier and capabilities. Requires X/P/O bandwidth tier and no D (congestion), E (severe congestion), G (no tunnels), or U (firewalled/unreachable) capability flags.

      Bandwidth tier is a fact about the peer rather than an observation that can erode, so these peers are eligible for fast-tier fast-track regardless of measured throughput.

      Parameters:
      peer - the peer to check
      Returns:
      true if X/P/O tier and no D/E/G/U caps
    • qualifiesHighBandwidthTier

      static boolean qualifiesHighBandwidthTier(String tier, String caps)
      Whether a bandwidth tier and capability string together qualify the peer for the high-capacity / fast tiers: X/P/O tier and none of the D (moderate congestion), E (severe congestion), G (no tunnels) or U (unreachable) capability flags.

      Pure decision — no context access, so a (tier, caps) pair already in hand can be tested without a router.

      Parameters:
      tier - the advertised bandwidth tier
      caps - the peer's capability string
      Returns:
      true if X/P/O tier and no D/E/G/U caps
    • isHighBandwidthTierName

      static boolean isHighBandwidthTierName(String tier)
      Whether a bandwidth tier is one of the high-capacity tiers (O, P, X).

      Pure decision — no context access, safe for unit tests.

      Parameters:
      tier - the advertised bandwidth tier, already stripped of HTML
      Returns:
      true for O, P, or X
    • isFastTierCapable

      private boolean isFastTierCapable(Hash peer)
      Whether the peer qualifies for the fast tier based on advertised bandwidth tier and capabilities. Currently identical to isHighBandwidthCapable(Hash) — both tiers reject X/P/O with D/E/G/U caps. Kept separate so fast tier can diverge (e.g. stricter RTT gate) in the future.
      Parameters:
      peer - the peer to check
      Returns:
      true if X/P/O tier and no D/E/G/U caps
    • getHighCapRTTThreshold

      public double getHighCapRTTThreshold()
      Get the high-capacity RTT ceiling for display and diagnostics. High-cap has no RTT gate — acceptance ratio and loss gates handle quality. Returns 0 (no ceiling).
      Returns:
      0 (high-cap has no RTT ceiling)
    • lockedGetProfile

      private PeerProfile lockedGetProfile(Hash peer)
      Returns a peer's profile with the read lock held.
      Parameters:
      peer - the peer hash
      Returns:
      the profile, or null if unknown
    • getMinCandidateSample

      public static int getMinCandidateSample(RouterContext ctx)
      Read the candidate-sample floor, honouring the configured override. Clamped to [16, MAX_CANDIDATE_SAMPLE] so a bad value degrades to a sane range rather than scanning the whole tier or collapsing to a single candidate.
      Parameters:
      ctx - the router context
      Returns:
      the floor to use for maxCandidateSample
    • setMinCandidateSample

      public static void setMinCandidateSample(int val)
      Override the candidate-sample floor at runtime.
      Parameters:
      val - the new floor, clamped to [16, MAX_CANDIDATE_SAMPLE]
    • maxCandidateSample

      static int maxCandidateSample(int howMany, int peerCount)
      Cap on how many tier peers to gate-check when selecting howMany tunnels. Scanning all ~670 fast peers per selection was a CPU hot path; a 20× sample (min 20) cut work ~10× on large tiers but, because HashMap iteration order is stable, every selection examined the same prefix of the tier — the rest of it stayed unreachable while that prefix kept passing, so tier size never became choice. With the scan start rotated, the sample is the size of one attempt's lottery: 64 gives lockedPickLowestPriority a real low-latency pick while remaining an order of magnitude cheaper than a full tier scan. A 10× sample was tried before the original 20× and hurt build success, so the 20× multiplier for larger requests is kept.
      Parameters:
      howMany - tunnels requested (may be 0 or negative)
      peerCount - size of the tier map (may be 0)
      Returns:
      maximum candidates to examine, never negative
    • maxCandidateSample

      static int maxCandidateSample(int howMany, int peerCount, int floor)
      Candidate-sample cap for a configurable floor. Split out from maxCandidateSample(int, int) so the arithmetic is testable without a RouterContext.
      Parameters:
      howMany - peers the caller wants
      peerCount - size of the tier being sampled
      floor - minimum candidates to examine regardless of howMany
      Returns:
      maximum candidates to examine, never negative
    • lockedSelectPeers

      private void lockedSelectPeers(Map<Hash, PeerProfile> peers, int howMany, Set<Hash> toExclude, Set<Hash> matches, int mask, MaskedIPSet ipSet, double buildSuccess, long rttCeiling)
      Selects peers while holding the lock.
      Parameters:
      peers - the profiles to choose from
      howMany - the number of peers to select
      toExclude - the hashes to skip
      matches - the set to add the selections to
      mask - the network bits to match
      ipSet - the masked ips to match
      buildSuccess - the cached build success ratio
      rttCeiling - the largest acceptable round trip time
    • lockedSelectPeers

      private void lockedSelectPeers(Map<Hash, PeerProfile> peers, int howMany, Set<Hash> toExclude, Set<Hash> matches, SessionKey randomKey, ProfileOrganizer.Slice subTierMode, int mask, MaskedIPSet ipSet, double buildSuccess, long rttCeiling)
      Collects up to maxCandidateSample(int, int) candidates from a tier and hands them to lockedPickLowestPriority(List, int, Set).

      The scan start is a random offset into the tier. Tier maps are plain HashMaps, so iteration order is stable across calls; without the offset every hop of every pool examined the same prefix, which both starved the rest of the tier and piled "too many tunnels" onto the same few peers. Peers in the tier were already vetted by isSelectable at tier-entry time, so only fast-changing gates are re-checked here (banlist, ghosts, first-hop cooldown, lifetime failures) rather than full proof-of-life, which would reject too many peers on stale RouterInfo.

      Parameters:
      peers - tier to scan; must be a field map so the starved warning can name it
      howMany - total peers the caller wants held in matches across the whole cascade, not this tier alone
      toExclude - live exclusion set, mutated with peers rejected by a hard gate
      matches - output set, already holding any previously selected peers
      randomKey - sub-tier slice key, or null to skip slicing
      subTierMode - sub-tier mask, or null to skip slicing
      mask - IP /n diversity restriction, 0 to disable
      ipSet - mutable subnet set, null when mask is 0
      buildSuccess - build success ratio fetched once by the caller
      rttCeiling - soft ceiling; peers above it are skipped, never excluded
    • warnTierStarved

      private void warnTierStarved(Map<Hash, PeerProfile> peers, int examined, int skipExcluded, int skipGated, int skipSliced, int skipIp, int skipRtt)
      Rate-limited warning naming the gate that emptied a tier scan. A tier reporting zero candidates is invisible upstream. The caller just falls through to the next tier — so without this a 1000-peer tier rejected by a single gate looks identical to genuine scarcity.
      Parameters:
      peers - the tier that produced no candidates
      examined - entries walked before giving up
      skipExcluded - rejected by the live exclusion set
      skipGated - rejected by a hard gate (ban, ghost, first-hop, lifetime failures)
      skipSliced - rejected by the sub-tier slice
      skipIp - rejected by IP diversity
      skipRtt - above the adaptive RTT ceiling
    • profileToPromote

      static PeerProfile profileToPromote(PeerProfile live, PeerProfile fallback)
      Which profile a promotion pass should act on: the live one when the map has it, the caller's otherwise.

      The TreeSet's copy may be stale, so the live profile decides. Making the choice a named decision is what lets the promotion and its log reach the same object — they used to decide separately, so the log printed the copy's capacity while the promotion ran on the live one.

      Parameters:
      live - the profile from _notFailingPeers, may be null
      fallback - the profile the caller already holds
      Returns:
      the profile to act on, never null
    • tierScanStarved

      static boolean tierScanStarved(int candidates, int howMany, int examined, int skipExcluded, int skipGated, int skipSliced, int skipIp, int skipRtt)
      Whether a tier scan starved badly enough to be worth reporting.

      All five skip reasons participate. The guard used to sum only four of the five it was about to pass to the logger, so a tier emptied purely by sub-tier slicing produced no warning. The one starvation cause with a dedicated counter was the one that stayed silent. Extracting the sum is what makes "every reason counts" checkable.

      Parameters:
      candidates - how many candidates the scan produced
      howMany - peers the caller asked for
      examined - entries walked before giving up
      skipExcluded - rejected by the live exclusion set
      skipGated - rejected by a hard gate
      skipSliced - rejected by the sub-tier slice
      skipIp - rejected by IP diversity
      skipRtt - above the adaptive RTT ceiling
      Returns:
      true when the scan found nothing and something specific caused it
    • lockedPickLowestPriority

      private void lockedPickLowestPriority(List<Map.Entry<Hash, PeerProfile>> candidates, int howMany, Set<Hash> matches)
      Selects the lowest-priority candidates, penalizing moderately-lossy peers. Must be called with the read lock held.

      Both lossy thresholds are resolved once for the whole list rather than per candidate. The lottery draw uses ThreadLocalRandom rather than _context.random(): it only breaks ties among peers that have already passed every gate, so it carries no cryptographic requirement, while _context.random().nextFloat() is a monitored read on the process-wide Fortuna instance and serialized every concurrent selection.

      Parameters:
      candidates - the entries to choose from, each a peer hash with its profile
      howMany - maximum number of peers to pick; values below one pick nothing
      matches - output set receiving the chosen peer hashes
    • pickLowestPriority

      public static int pickLowestPriority(List<Hash> candidates, float[] priority, int howMany, Set<Hash> matches)
      Choose up to howMany candidates in ascending priority order.

      This loop is the reason the peer-selection callers may size a fixed-capacity ArraySet to exactly the number of peers they asked for:

      • net.i2p.router.tunnel.pool.ClientPeerSelector builds four fallback sets as new ArraySet<>(needed) and passes needed as howMany.
      • net.i2p.router.tunnel.pool.ExploratoryPeerSelector builds new ArraySet<>(1) and asks for one peer.
      An ArraySet throws SetFullException once written past its capacity, so if this loop ever wrote more than howMany entries those callers would throw at runtime rather than degrade. The bound s < howMany && s < sz is therefore load-bearing, and it is pinned by PeerSelectionBoundTest.picksNeverExceedHowMany and its neighbours.

      Distinctness falls out of the swap rather than from a membership test: the chosen entry is moved to index s and the next inner scan starts at s + 1, so an entry already picked can never be revisited. candidates and priority are reordered in place to achieve that, which is why both are taken as mutable.

      Parameters:
      candidates - peer hashes, reordered in place
      priority - per-candidate score, parallel to candidates, reordered in place
      howMany - maximum entries to choose; values below one choose nothing
      matches - receives the chosen hashes
      Returns:
      how many entries were actually added to matches
    • lockedSelectActive

      private void lockedSelectActive(List<Hash> connected, int howMany, Set<Hash> toExclude, Set<Hash> matches, int mask, MaskedIPSet ipSet, double buildSuccess)
      Scan up to maxCandidateSample(int, int) established peers for the active tier, in RandomIterator order so the peers examined differ from call to call. Capped for the same reason as lockedSelectPeers(Map, int, Set, Set, int, MaskedIPSet, double, long): one attempt's worth of lottery, not a walk of the whole list.
      Parameters:
      connected - the connected peers to draw from, in random-iterator order
      howMany - peers to add; the scan stops once matches reaches this count
      toExclude - peers to skip, or null; a peer failing the selectability gate is added here so later passes skip it too
      matches - output set receiving the chosen hashes; its size counts against howMany
      mask - leading address bytes to match for /n subnet diversity, 0 to disable
      ipSet - fingerprints claimed so far, consulted only when mask is non-zero
      buildSuccess - the build success ratio in [0.0, 1.0], fetched once per scan
    • notRestricted

      private boolean notRestricted(Hash peer, MaskedIPSet ipSet, int mask)
      Subnet-diversity gate for the running router.
      Parameters:
      peer - the peer whose addresses, ports and family are fingerprinted
      ipSet - the fingerprints claimed by the peers evaluated so far
      mask - leading address bytes to match, as taken by MaskedIPSet.maskedIPKey(byte[], int)
      Returns:
      true if the peer passed the gate and may be selected
    • notRestricted

      static boolean notRestricted(RouterContext ctx, Hash peer, MaskedIPSet ipSet, int mask)
      Subnet-diversity gate: does this peer pass the masked-subnet, port and family check, and if it does, claim its fingerprints so that no later peer sharing any one of them passes as well. Every selection path taking a mask and an ipSet calls this, and all of them read true as "allowed".

      Before the peer-selection refactor this built a throwaway MaskedIPSet, tested containsAny against the accumulator, then added the keys. Inlining the build folded the collision test into the adds, which inverted the result and let a peer collide with itself; both are corrected here while keeping the single pass over the RouterInfo.

      Parameters:
      ctx - supplies the comm system IP and the NetDb lookup
      peer - the peer whose addresses, ports and family are fingerprinted
      ipSet - the fingerprints claimed by the peers evaluated so far
      mask - leading address bytes to match, as taken by MaskedIPSet.maskedIPKey(byte[], int)
      Returns:
      true if the peer passed the gate and may be selected
    • fingerprints

      static Set<String> fingerprints(RouterContext ctx, Hash peer, int mask)
      One peer's fingerprints: the masked IP the comm system recorded, the masked IP and port of every published address, and the family option.

      Collected in their own set rather than added straight to the shared accumulator, because a peer normally publishes two transports on one address and its own keys overlap by design — a peer must not collide with itself.

      Parameters:
      ctx - supplies the comm system IP and the NetDb lookup
      peer - the peer to fingerprint
      mask - leading address bytes to match, 1-4
      Returns:
      the peer's fingerprint keys, empty if nothing is known about it
    • tryClaimKeys

      static boolean tryClaimKeys(Set<String> claimed, Set<String> candidateKeys)
      The diversity gate itself: report whether any of this peer's fingerprints is already claimed, and claim them all either way. A peer passes only when none of its keys was claimed, so one peer per masked subnet, per port and per family gets through.

      Claiming a rejected peer's keys is load-bearing: it keeps the outcome the same whichever order the candidates are evaluated in, so a peer cannot pass the gate merely because selection happened to look at it first.

      Parameters:
      claimed - the fingerprints claimed by the peers evaluated so far
      candidateKeys - one peer's fingerprints, already deduplicated
      Returns:
      true if none of the candidate keys was claimed yet, i.e. the peer passed; an empty candidate list passes vacuously
    • getSubTier

      private int getSubTier(Hash peer, long k0, long k1)
      Sub-tier slice index for a peer, keyed off the session key so the same key always yields the same slice.
      Parameters:
      peer - the peer to place
      k0 - first half of the session key the slice is keyed off
      k1 - second half of that session key
      Returns:
      the slice index, 0-3
    • isSelectable

      public boolean isSelectable(Hash peer)
      Whether the given peer is eligible for selection. Fetches the build success ratio once; per-peer callers should use isSelectable(Hash, double) with a value fetched once per scan.
      Parameters:
      peer - hash of the peer to judge
      Returns:
      true if the peer passes every selection gate
    • isSelectable

      private boolean isSelectable(Hash peer, double buildSuccess)
      Whether the given peer is eligible for selection.
      Parameters:
      peer - hash of the peer to judge
      buildSuccess - the build success ratio, fetched once per scan
      Returns:
      true if the peer passes every selection gate
    • passesBasicGates

      private boolean passesBasicGates(Hash peer)
      Returns false when the peer is us, banlisted, or recently failed as first hop.

      The self check is first because this is the chokepoint every selection path funnels through, including selectRemainderFromAllPeers(int, Set, Set, double) via isSelectable(Hash, double). The local RouterInfo is present in our own netdb, so we are a legitimate entry in every candidate pool.

      Note that we are supposed to be in the tunnel we build, at one end: ClientPeerSelector.finalizeSelection inserts us directly, and ExploratoryPeerSelector adds us to its exclude set. Neither goes through this method, so excluding us here costs nothing. What this guard prevents is being drawn as an extra hop — a mid-hop in a tunnel we are already an end of, which can only produce a build that loops back on itself. The tier drawers (lockedSelectPeers, lockedSelectActive) each excluded self locally; the remainder path used when tiers cannot supply enough peers did not, so the two protections could disagree. Checking here rather than in each selection loop means no present or future path can reintroduce the gap.

      Scope, stated precisely because it was previously overstated: this guard has not been shown to fix any observed build failure. A router appearing in an expiring-build log line is expected — it is the gateway — so that log is not evidence of self-selection. Whether we were being drawn at a mid-hop has not been established.

      Parameters:
      peer - the candidate's hash, tested against self, the banlist, the ghost-peer set and the first-hop failure list
      Returns:
      true if the peer is not us, not banlisted, not a ghost, and not recently failing as a first hop
    • flushSelectionCounters

      private void flushSelectionCounters()
      Record the banned-candidate rejections accumulated since the last flush as a single tunnel.peerBannedAtSelection sample. The count stays exact: the atomic get-and-reset loses nothing, it only attributes a rejection to whichever scan drains it first. A count of zero writes nothing.
    • isExcessiveLifetimeFailure

      static boolean isExcessiveLifetimeFailure(PeerProfile prof)
      Whether the given profile carries excessive cumulative tunnel failures. Dual gate: hard exclusion at MAX_LIFETIME_TUNNEL_FAILURES failures or when the lifetime failure ratio exceeds MAX_LIFETIME_FAILURE_RATIO. A null profile or null history is not a failure.

      The blame system (tunnelFailed()) only increments statistics — it never bans. Without this check, peers with 200+ failures keep getting selected because the ghost peer system clears on any success and the first-hop cooldown is only 5 minutes. Pure decision — no context access, safe for unit tests.

      Parameters:
      prof - the profile, may be null
      Returns:
      true when the peer should be excluded from selection
    • getExcessiveFailurePenalty

      static double getExcessiveFailurePenalty(Hash peer, PeerProfile prof)
      Selection penalty multiplier for peers between the soft and hard failure thresholds. Peers with SOFT_FAILURE_PENALTY_THRESHOLD to MAX_LIFETIME_TUNNEL_FAILURES lifetime failures are not excluded but receive a penalty that lowers their selection priority. Returns 1.0 (no penalty) for peers below the soft threshold or above the hard threshold (they'd be excluded anyway). Pure decision — no side effects.
      Parameters:
      peer - the peer hash
      prof - the peer's profile, or null if it has none
      Returns:
      a penalty multiplier in (1.0, 2.0] — higher means more penalized
    • hasValidRouterInfo

      private boolean hasValidRouterInfo(Hash peer, RouterInfo info, double buildSuccess, PeerProfile prof)
      Checks the RouterInfo for hidden flag, stale proof of life, usable transport address, bandwidth tier, and tunnel exclusion.
      Parameters:
      peer - the peer's hash, used for the established-connection check
      info - the peer's RouterInfo, already looked up and not null
      buildSuccess - the build success ratio in [0.0, 1.0], which scales the tunnel-exclusion check
      prof - the peer's profile, or null if it was never seen; consulted only for proof of life when the RouterInfo is stale
      Returns:
      true if the RouterInfo passes every age, liveness and usability check
    • isExcludedBuildTier

      static boolean isExcludedBuildTier(String rawTier)
      Whether a RouterInfo's advertised bandwidth tier is one that cannot host a tunnel: L, M, or N.

      The raw string is compared directly rather than through DataHelper.stripHTML, which is not a weakening: stripHTML only substitutes spaces for < > " ', so stripHTML(t).equals("L") holds exactly when t is the one-character string "L".

      Pure decision. No context access, safe for unit tests.

      Parameters:
      rawTier - the tier string as advertised, possibly HTML-obfuscated
      Returns:
      whether the tier excludes the peer from tunnel hosting
    • getMaxRouterInfoAgeMs

      private long getMaxRouterInfoAgeMs()
      Maximum RouterInfo age in ms, cached alongside getTunnelBuildSuccess() on the same BUILD_SUCCESS_CACHE_MS cadence: it is a constant read per candidate, and 15s of staleness on a proof-of-life bar is immaterial.
      Returns:
      the max RouterInfo age in ms
    • hasUsableTransportAddress

      private boolean hasUsableTransportAddress(RouterInfo info)
      Returns true when the RouterInfo has a reachable NTCP/NTCP2/SSU/SSU2 address.
      Parameters:
      info - the peer's RouterInfo whose addresses are scanned
      Returns:
      true if at least one NTCP, NTCP2, SSU or SSU2 address carries a non-null IP and a valid port
    • hasRecentProofOfLife

      static boolean hasRecentProofOfLife(PeerProfile profile, long now)
      True if the peer has shown proof of life within PROOF_OF_LIFE_WINDOW_MS: a successful send, heard-from, or heard-about within the last hour. A null profile (never seen) is not evidence of life. Used to trust a RouterInfo that is older than the maximum age — the RouterInfo alone is not enough, the peer must have been active recently.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      profile - the peer profile, null if none
      now - current time in ms
      Returns:
      whether the peer has recent proof of life
    • lockedPlaceProfile

      private void lockedPlaceProfile(PeerProfile profile, double buildSuccess)
      Puts a profile into the pool maps with the write lock held.
      Parameters:
      profile - the profile to place
      buildSuccess - the current tunnel build success ratio
    • lockedPlaceProfile

      private void lockedPlaceProfile(PeerProfile profile, double buildSuccess, int minHighCap, int maxFast, int maxHighCap)
      Variant taking the tier thresholds, which the caller has already fetched for the whole scan.
      Parameters:
      profile - the profile to place
      buildSuccess - cached tunnel build success ratio
      minHighCap - minimum high-capacity peers for this scan
      maxFast - maximum fast peers for this scan
      maxHighCap - maximum high-capacity peers for this scan
    • lockedPromoteProfileToTiers

      private void lockedPromoteProfileToTiers(PeerProfile profile, double buildSuccess)
      Evaluate a profile for promotion to fast/high-cap tiers without touching the notFailing structures. Safe to call between reorganize cycles — does not add duplicates to _notFailingPeersList. Must be called with write lock held.
      Parameters:
      profile - the profile to evaluate for tier placement
      buildSuccess - cached tunnel build success ratio, read once by the caller
    • lockedPromoteProfileToTiers

      private boolean lockedPromoteProfileToTiers(PeerProfile profile, double buildSuccess, int minHighCap, int maxFast, int maxHighCap)
      Evaluate a profile for promotion to the tiers, reporting whether the only thing that stopped it was an unusable transport address.

      The return value exists for promoteToFillTiers(), whose underfill log line wants to distinguish a candidate held back by policy from a candidate that was never examined. That line used to carry a local counter which was declared and then never incremented, so it always reported zero holds and read as "the gate never fires" for an entire diagnostic run.

      Parameters:
      profile - the profile to evaluate
      buildSuccess - the windowed build success ratio
      minHighCap - minimum high-capacity peers
      maxFast - maximum fast peers
      maxHighCap - maximum high-capacity peers
      Returns:
      true only if promotion was held because the RouterInfo has no usable transport address; false for every other outcome
    • hasNoUsableTransport

      private boolean hasNoUsableTransport(Hash peer)
      Does this peer's RouterInfo carry no address we could still send to?

      Reads the netdb unvalidated, matching what selection and the build requestor do, so the gate cannot reject a peer on stricter grounds than the path that would later use it. A missing entry counts as "no usable address": we cannot prove otherwise, and promoting an unverifiable peer is precisely the case this gate exists to stop.

      Parameters:
      peer - hash of the candidate under consideration
      Returns:
      true if the peer should be held out of the selection tiers
    • promotionNeedsRouterInfoRefresh

      private boolean promotionNeedsRouterInfoRefresh(PeerProfile profile, long now)
      Whether this peer's stored RouterInfo is stale enough that promoting on it risks a build failure.

      Reads the same staleness notion hasValidRouterInfo(Hash, RouterInfo, double, PeerProfile) uses — older than the threshold and no proof of life — against the shorter PROP_ROUTERINFO_REFRESH_AGE_MS. Proof of life exempts working peers, which is what keeps the affected population small: a peer we have talked to in the last hour is judged on what it just told us, not on how old its last publication is.

      Parameters:
      profile - the profile being promoted
      now - current time in ms
      Returns:
      whether the stored RouterInfo is too old to promote on
    • needsRouterInfoRefresh

      static boolean needsRouterInfoRefresh(RouterInfo info, PeerProfile profile, long now, long refreshAgeMs)
      Whether a RouterInfo is old enough that promoting on it risks a build failure: published before the threshold and no proof of life.

      An absent RouterInfo is not stale, it is missing, and is not this predicate's business: isHighBandwidthCapable(Hash) and isFastTierCapable(Hash) already refuse to promote a peer with no RouterInfo at all, so there is no promotion here to hold.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      info - the peer's stored RouterInfo, may be null
      profile - the peer profile supplying proof of life, may be null
      now - current time in ms
      refreshAgeMs - staleness threshold in ms
      Returns:
      whether the RouterInfo is too stale to promote on
    • holdPromotionForRefresh

      private boolean holdPromotionForRefresh(Hash peer, boolean staleRouterInfo, int tierSize, int safeFloor)
      Apply the hold to one tier insert: leave the peer out of the tier, or let it in.

      The starvation guard is the reason this is not a plain veto. A tier that is already at its configured minimum is one the fallback passes would defend anyway, so leaving a stale peer out of it costs nothing there. Below the minimum the hold would compete with those passes for the same candidates and the tier could end up short, which is the worse of the two failures: a stale promotion risks one bad build, an empty tier sends every build back to the general pool. When the guard opens, the promotion is counted so the team can see exactly how often that happens.

      Parameters:
      peer - the peer being promoted
      staleRouterInfo - whether the peer carries a stale RouterInfo
      tierSize - current size of the tier being joined
      safeFloor - size at or above which this tier can leave it out
      Returns:
      whether the promotion must be held back
    • shouldDeferPromotionOnStaleRouterInfo

      static boolean shouldDeferPromotionOnStaleRouterInfo(int tierSize, int safeFloor)
      Whether a promotion may be held back on a stale RouterInfo without risking the tier it would join.

      The floor is the tier's configured minimum, which is also what fillFastTierFallbacks(long, Set, double) and fillHighCapFallback(long, Set, double) drive the tier back up to and what restorePreservedPeers(long, Set, Set, double) protects at half. At or above the minimum the tier can afford to leave the peer out; below it, it cannot.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      tierSize - current size of the tier the peer would join
      safeFloor - size at or above which the tier can leave it out
      Returns:
      whether the promotion should be held back
    • getRouterInfoRefreshAgeMs

      private long getRouterInfoRefreshAgeMs()
      RouterInfo age past which a promotion is held back, in ms. Read once per reorganisation cycle rather than cached per candidate, because that is the only place it is consulted.

      Separate from PROP_MAX_ROUTERINFO_AGE_HOURS, which is the bar a RouterInfo must clear to be selected at all. That bar is deliberately loose (2h) because a proof of life can stand in for a fresh RouterInfo; this one is a promotion-time policy with no such fallback, so it is set where the RouterInfo itself is likely to have been superseded: routers republish hourly, so anything older than one publishing interval has a good chance of describing a peer that has since changed.

      Holding a promotion costs the peer a tier slot and nothing else — no lookup is issued, so the only way a held peer re-enters is by being republished to, or by showing proof of life.

      Returns:
      the staleness threshold in ms; <= 0 disables the hold
    • recordRouterInfoRefreshStats

      private void recordRouterInfoRefreshStats()
      Publish the promotion-deferral counters for the cycle just finished.

      peer.routerInfoRefreshNeeded is a population, so it is written every cycle including zero. A flat zero line is the signal that nothing is being held back. The stale-promotion count is an event count and follows the existing convention of writing nothing when it is zero.

    • recordPromotionSkip

      private void recordPromotionSkip(String reason)
      Counts one promotion skip against its reason.

      The per-peer line stays at DEBUG because a single pass walks the whole capacity-ordered profile set — about 4,300 evaluations on the live router, several passes a minute. Logging each one at INFO would bury the log; the question being answered is which gate is doing the excluding, and that is a distribution rather than a list.

      Not synchronised: every caller holds the write lock, and the tally is cleared by whichever scan is about to report it.

      Parameters:
      reason - the reason promotionSkipReason(PeerProfile, Hash, double, long) returned
    • promotionSkipLabel

      static String promotionSkipLabel(String reason)
      Display form of a promotion skip reason: prose words with the first letter capitalised, so the tally reads as a list of reasons rather than as code identifiers. The identifiers themselves stay greppable in the per-peer DEBUG line and in promotionSkipReason(PeerProfile, Hash, double, long).
      Parameters:
      reason - the reason promotionSkipReason(PeerProfile, Hash, double, long) returned
      Returns:
      the tally display form; the reason unchanged when unmapped
    • drainPromotionSkipTally

      private String drainPromotionSkipTally()
      Formats the promotion skip tally and clears it.
      Returns:
      \n* continuation lines of Label: count pairs (packed to about 80 chars per line), or the empty string when nothing was skipped since the last call
    • skipsPromotion

      boolean skipsPromotion(PeerProfile profile, Hash peer, double buildSuccess)
      Whether the peer should be skipped for tier promotion: not selectable, in a strict country, low tunnel acceptance, high-latency penalty, congested, or in loss probation. Mirrors the eligibility gates of lockedPlaceProfile(PeerProfile, double).

      No side effects. Safe to evaluate without holding locks.

      Parameters:
      profile - the profile under consideration
      peer - the peer hash
      buildSuccess - the tunnel build success ratio in [0.0, 1.0]
      Returns:
      whether promotion should be skipped
    • promotionSkipReason

      String promotionSkipReason(PeerProfile profile, Hash peer, double buildSuccess, long now)
      Why skipsPromotion(PeerProfile, Hash, double) would skip this profile, or null when it would not.

      The one place the decision is made. The caller used to re-derive two of the five gates so it could log them, which meant a peer held back by congestion, loss probation, a strict country or low tunnel acceptance printed notPeerSelectable=false excessiveFailures=false — a line reading "the gates passed" while the peer was in fact skipped. Returning the reason from the decision itself is what stops the log from guessing, and stops the two from drifting apart again.

      Short-circuits on the first gate that fires, in the order the original disjunction evaluated them. The boolean result is unchanged, because the original was a pure OR over the same five conditions.

      Parameters:
      profile - the profile under consideration
      peer - the peer hash
      buildSuccess - the tunnel build success ratio in [0.0, 1.0]
      now - current time in ms
      Returns:
      a short reason, or null when promotion may proceed
    • promoteToFillTiers

      private void promoteToFillTiers()
      After a demotion vacates slots in fast/high-cap tiers, scan the capacity-ordered peer list and promote the next best candidates to fill the openings. Must be called with write lock held.
    • promotionTransition

      static String promotionTransition(boolean wasFast, boolean wasHighCap, boolean nowFast, boolean nowHighCap)
      Describe a tier promotion as a from -> to transition, naming only the tiers actually gained. Logging unchanged membership (a tier that was and stayed true) reports no event and buries the one that happened, so a scan that changed nothing returns null and the caller logs nothing. This method only adds; a lost tier is never reported here.
      Parameters:
      wasFast - in the fast tier before the promotion
      wasHighCap - in the high-capacity tier before the promotion
      nowFast - in the fast tier after the promotion
      nowHighCap - in the high-capacity tier after the promotion
      Returns:
      e.g. "highCap -> fast", "none -> highCap"; null when no tier was gained
    • promotionEvidence

      static String promotionEvidence(int bonus, int raw, boolean promotedIsLive)
      Evidence line for a promotion log: only the values that explain something. A zero bonus or raw adds nothing to read, and a live-profile match is the normal case. Only the anomaly (the promoted object was not the live profile) earns its place. Returns null when every value is default, so the log line stays one line for a routine promotion.
      Parameters:
      bonus - the capacity bonus applied
      raw - the unclamped bonus the bonus was derived from
      promotedIsLive - whether the promoted object was the live profile
      Returns:
      a "\n* " continuation line, or null when all default
    • describeTierFill

      static String describeTierFill(String where, int fast, int fastTarget, int highCap, int highCapTarget, int examined, int alreadyTiered, int heldNoAddress, String skips)
      Summarise why the selection tiers could not be filled.

      Short because there were no candidates left to try is a discovery problem. Short because every candidate was gated out is a policy problem. Those call for different fixes, and from outside the reorg they look identical, so the breakdown is reported rather than inferred. How to read it: held-no-address above zero is a policy gate; examined zero means no candidates were found. That guide lives here, not in the emitted line. Zero counters report the absence of an event and stay out; the skip tally rides along as evidence for this same shortfall.

      Parameters:
      where - the caller, for attribution
      fast - the fast-tier size achieved
      fastTarget - the fast-tier size wanted
      highCap - the high-capacity-tier size achieved
      highCapTarget - the high-capacity-tier size wanted
      examined - candidates walked, including those rejected by a gate
      alreadyTiered - candidates skipped because already in both tiers, omitted when zero
      heldNoAddress - candidates that cleared every gate except having no usable address, omitted when zero
      skips - the drained skip tally with its own "\n* " prefix, or empty
      Returns:
      the shortfall and its causes, wrapped onto continuation lines
    • demoteIfStale

      public void demoteIfStale(Hash peer)
      Immediately demote a peer from fast/high-cap tiers if its RouterInfo is stale and it has no recent proof of life (fails isSelectable). Non-blocking — only acts if the peer is currently in those tiers.
      Parameters:
      peer - hash of the peer to demote
    • demoteIfHighLatency

      public void demoteIfHighLatency(Hash peer)
      Immediately demote a peer from fast/high-cap tiers. The caller must have already judged the peer high-latency; this only performs the tier removal. Non-blocking.
      Parameters:
      peer - hash of the peer to demote
    • demoteIfCongested

      public void demoteIfCongested(Hash peer)
      Immediately demote a peer from fast/high-cap tiers. The caller must have already judged the peer congested (cap D/E); this only performs the tier removal. Non-blocking - only acts if the peer is currently in those tiers.
      Parameters:
      peer - hash of the peer to demote
    • demoteIfNotLowLatency

      public void demoteIfNotLowLatency(Hash peer)
      Immediately demote a peer from the fast tier when a peer test shows it is no longer low-latency. Called from PeerTestJob when a test result arrives that contradicts the peer's tier membership. Non-blocking — only acts if the peer is currently in the fast tier.

      High-capacity membership is deliberately left alone. It is earned from capacity and throughput rather than latency, so a latency judgement does not speak to it; see the comment in the body.

      Parameters:
      peer - hash of the peer to demote
    • demotesFastTier

      static boolean demotesFastTier(boolean inFast, boolean lowLatency)
      Whether losing the low-latency flag revokes membership of the fast tier.

      Fast-tier membership is the latency promotion, so losing the flag revokes it. High-capacity membership is not revoked by this signal: it is earned from advertised capacity and sustained throughput, so a peer that has stopped being low latency has not stopped being high capacity. This method used to evict from both tiers, which with a calibrated per-transport floor meant a transport whose baseline rose during congestion could strip bandwidth-qualified peers out of the tier they had been admitted to.

      Parameters:
      inFast - whether the peer is currently in the fast tier
      lowLatency - whether the peer still holds the low-latency flag
      Returns:
      true if the peer should leave the fast tier
    • isLowLatency

      private boolean isLowLatency(Hash peer)
      Check whether the peer's profile has low-latency status. Non-blocking — returns the profile's flag without acquiring locks.
      Parameters:
      peer - the peer whose profile flag is read
      Returns:
      true if a profile was found and it carries the low-latency flag
    • demoteIfBanned

      public void demoteIfBanned(Hash peer)
      Immediately demote a peer from fast/high-cap tiers. The caller must have already confirmed the ban; this only performs the tier removal. Non-blocking - only acts if the peer is currently in those tiers.
      Parameters:
      peer - hash of the peer to demote
    • recordFirstHopRtt

      private void recordFirstHopRtt(PeerProfile profile, int rtt, long when)
      The single recorder for a measured direct-link RTT.

      Every path that holds a transport measurement. The reorganize sampling pass, the pre-connect warm-up, a handshake — funnels through here, so the field is written one way and re-derived one way. Callers that only need the write (no stat, because they report a cycle aggregate instead) call recordFirstHopRtt(PeerProfile, int, long); callers that hold a peer hash instead of a profile call noteFirstHopRtt(Hash, int, long).

      Owns the profile write so callers holding a transport measurement do not have to know how the value is stored or aged out. The value decays on the existing Active tier window, so a first-hop latency never outlives the evidence class it belongs to.

      Parameters:
      profile - the profile to record against
      rtt - the measured round trip time in ms
      when - timestamp of the measurement
    • noteFirstHopRtt

      public void noteFirstHopRtt(Hash peer, int rtt, long when)
      Record a measured direct-link RTT against a peer, by hash.
      Parameters:
      peer - the measured peer
      rtt - the measured round trip time in ms
      when - timestamp of the measurement
    • demoteIfUnreachableNow

      public void demoteIfUnreachableNow(Hash peer)
      Immediately demote a peer from fast/high-cap tiers on a single unreachable first hop, skipping the DEMOTE_STRIKE_THRESHOLD strikes and the cooldown that demoteIfUnreachable(Hash) requires. Non-blocking — only acts if the peer is currently in those tiers.
      Parameters:
      peer - hash of the peer to demote
    • demoteIfNoTunnel

      public void demoteIfNoTunnel(Hash peer)
      Immediately demote a peer from fast/high-cap tiers. The caller must have already judged the peer to have built no tunnel recently. This only performs the tier removal. Non-blocking - only acts if the peer is currently in those tiers.
      Parameters:
      peer - hash of the peer to demote
    • demoteIfUnreachable

      public void demoteIfUnreachable(Hash peer)
      Immediately demote a peer from fast/high-cap tiers when our transport connection to it failed as a tunnel first hop (unreachable). Uses a 3-strike threshold within the decay window to avoid premature demotion. A transient failure (e.g. pre-connect race) should not remove a peer from tiers, and strikes older than the window expire so a peer that recovered is not demoted by one later failure. Does not touch the tunnel failure counter. The caller records the failure blame (e.g. profileManager().tunnelFailed()).
      Parameters:
      peer - hash of the peer that failed as a first hop
    • releaseWriteLockAndRefresh

      private void releaseWriteLockAndRefresh()
      Release the reorganize write lock, then refresh peers held out of the tiers.

      The single exit point for every path that runs a tier refill under the lock. Doing the refresh here rather than at each call site is deliberate: a call site that forgot it would leave the queue to fill and never drain, and there were ten of them. It has to be here, and not inside promoteToFillTiers(), because a refill runs with the lock held and issuing a lookup is network work that must never run under it.

    • refreshHeldPeers

      private void refreshHeldPeers()
      Issue RouterInfo lookups for peers held out of the tiers for want of a usable address.

      Called after the reorganize write lock is released. Doubly bounded - a per-drain batch and a minimum interval between drains - so neither a large scan nor a burst of demotions can turn this into a request flood.

    • noteMissingRouterInfo

      boolean noteMissingRouterInfo(Hash peer)
      Record that a stored profile was loaded for a peer the netdb cannot resolve.
      Parameters:
      peer - the peer whose RouterInfo is missing
      Returns:
      true if recorded, false if the recovery list was already full
    • mayDrainMissingRouterInfo

      boolean mayDrainMissingRouterInfo(long now)
      Whether enough time has passed to request the next batch of missing RouterInfos.
      Parameters:
      now - current time in milliseconds
      Returns:
      true if the drain interval has elapsed
    • takeMissingRouterInfoBatch

      List<Hash> takeMissingRouterInfoBatch(long now)
      Hand over the next batch of peers whose RouterInfo should be requested.
      Parameters:
      now - current time in milliseconds
      Returns:
      the peers to look up, possibly empty
    • getMissingRouterInfoPending

      int getMissingRouterInfoPending()
      Peers still waiting for their RouterInfo to be requested.
      Returns:
      the number pending
    • getMissingRouterInfoDropped

      int getMissingRouterInfoDropped()
      Peers not recorded because the recovery list was full.
      Returns:
      the number dropped
    • demoteBatch

      public void demoteBatch(Set<Hash> peers)
      Batch-demote peers from fast/high-cap tiers under a single write lock. Peers not currently in any tier are silently skipped. One promoteToFillTiers() call after all removals.
      Parameters:
      peers - peers to evict — may be empty (no-op, no lock)
    • demoteFastOnlyBatch

      public void demoteFastOnlyBatch(Set<Hash> peers)
      Batch-remove peers from fast tier only, keeping them in high-cap. Used to demote peers with test failures from fast to high cap. Does NOT call promoteToFillTiers() — re-promotion would undo the demotion. The next full reorganize() fills any gaps.
      Parameters:
      peers - peers to remove from fast tier — may be empty (no-op, no lock)
    • nextStrikeCount

      static int nextStrikeCount(int previousStrikes, long lastStrikeTime, long now)
      Compute the next strike count after a failure, applying time-window decay. A previous strike older than DEMOTE_STRIKE_DECAY_MS no longer counts, so a peer that recovered is not demoted by one later failure.
      Parameters:
      previousStrikes - strike count before this failure (0 if none)
      lastStrikeTime - time of the previous strike (0 if none)
      now - the current clock reading in milliseconds, compared against DEMOTE_STRIKE_DECAY_MS
      Returns:
      the new strike count, at least 1
    • demoteIfHighRTT

      void demoteIfHighRTT(Hash peer, long responseTimeMs)
      Demote a peer from fast/high-cap tiers when its direct-link latency is poor.

      responseTimeMs is the round trip through an entire multi-hop tunnel, which TestJob.noteSuccess attributes to every peer in that tunnel. That is a reasonable profile statistic but the wrong input for first-hop selection: a peer with a fast direct link is blamed for the other hops it happened to be a member of, and the result is compared against a first-hop threshold. In production that evicted a large share of the fast tier on a latency distribution taken across the wrong hops, shrinking the candidate pool and leaving slower survivors.

      The tier decision therefore uses PeerProfile.getFirstHopRtt(long), measured on the direct link. The passed-in tunnel time is not consulted. A peer with no recorded first-hop RTT is left alone: unknown is not slow.

      Parameters:
      peer - the peer to evaluate
      responseTimeMs - full-tunnel test round trip, unused by the tier decision
    • firstHopRttIsSlow

      static boolean firstHopRttIsSlow(int firstHopRttMs, int thresholdMs)
      Whether a measured first-hop RTT warrants removal from the fast tiers.

      Pure so the policy is testable without a populated profile. An unknown measurement (negative) is never slow: we have not probed the peer, and evicting on absent evidence would drain the tier of exactly the peers we have not looked at yet.

      Parameters:
      firstHopRttMs - direct-link RTT in ms, negative when unknown
      thresholdMs - the latency ceiling in ms
      Returns:
      true if the peer should be evicted from the fast/high-cap tiers
    • demoteIfSlowFirstHop

      void demoteIfSlowFirstHop(Hash peer, int firstHopRttMs)
      Remove a peer from the fast/high-cap tiers if its measured direct-link RTT reaches the low-latency threshold.

      The threshold is unchanged at router.peerTestTimeout * 2; only the measurement changed, from a whole tunnel to one hop.

      Parameters:
      peer - the peer to evaluate
      firstHopRttMs - direct-link RTT in ms, negative when unknown
    • excludedOnAcceptance

      static boolean excludedOnAcceptance(long agreed, long rejected, int minRequests, boolean highStress)
      Whether a peer's tunnel acceptance is bad enough to exclude it, given its counts and the sample floor the tier in question applies.

      Pure decision. No context access, safe for unit tests. The tiers differ in the floor they pass, and that difference is the whole point: high capacity is the reservoir the fast tier seeds from and cannot afford to judge a peer on a handful of requests, while the fast tier is what build selection draws from and can. Both bars fall under stress, because a peer rejecting our requests during network-wide congestion has not demonstrated anything about itself.

      Parameters:
      agreed - lifetime requests the peer accepted
      rejected - lifetime requests the peer declined
      minRequests - samples required before acceptance may exclude at all
      highStress - whether build success is below ATTACK_THRESHOLD
      Returns:
      true if the peer should be excluded on acceptance
    • isLowTunnelAcceptance

      private boolean isLowTunnelAcceptance(PeerProfile profile, double buildSuccess, long now, int minRequestsForJudgement)
      Check if peer has low tunnel acceptance ratio, decided from its lifetime accept/reject counts via excludedOnAcceptance(long, long, int, boolean), then refined by the low-sample or mature path. Peers with no tunnel test history are judged by recent activity instead: an alive peer is allowed into the tiers to accumulate data, a stale one is excluded. The mature path also honours a recent bandwidth-rejection cooldown.

      Takes the build success ratio as a parameter — it is fetched once per selection and passed down; getTunnelBuildSuccess() performs 6 stat lookups and must never run per candidate peer.

      Parameters:
      profile - the peer profile
      buildSuccess - the tunnel build success ratio in [0.0, 1.0]
      now - current time in ms
      minRequestsForJudgement - how many lifetime requests the peer needs before its acceptance ratio is allowed to exclude it at all
      Returns:
      whether low tunnel acceptance (true = exclude from fast/high-cap tiers)
    • isLowAcceptanceLowSample

      private boolean isLowAcceptanceLowSample(PeerProfile profile, double ratio, boolean highStress)
      Small-sample decision: below MIN_TUNNEL_REQUESTS lifetime requests, apply the loose startup thresholds.
      Parameters:
      profile - the peer profile
      ratio - the acceptance ratio in [0.0, 1.0]
      highStress - whether build success is below the attack threshold
      Returns:
      whether low tunnel acceptance
    • isLowAcceptanceMature

      private boolean isLowAcceptanceMature(PeerProfile profile, TunnelHistory th, double ratio, long agreed, long rejected, boolean highStress)
      Mature decision: effective minimum ratio, with a cooldown for recent bandwidth rejections.
      Parameters:
      profile - the peer profile
      th - the tunnel history (non-null)
      ratio - the acceptance ratio in [0.0, 1.0]
      agreed - lifetime accepted requests
      rejected - lifetime rejected requests
      highStress - whether build success is below the attack threshold
      Returns:
      whether low tunnel acceptance
    • isQualityPeer

      private boolean isQualityPeer(PeerProfile profile, long recentCutoff, double buildSuccess)
      Check if peer is a quality tunnel candidate: good acceptance ratio, selectable, and recently active. Used by tuner to adjust tier limits based on viable peers, not just raw counts.
      Parameters:
      profile - the tier peer's profile to judge
      recentCutoff - the earliest successful send that still counts as recent activity
      buildSuccess - the build success ratio in [0.0, 1.0], passed to the selectability gate
      Returns:
      true if the peer is selectable, has sent successfully since recentCutoff, and clears the acceptance-ratio floor once it has MIN_TUNNEL_REQUESTS of tunnel history
    • hasRecentTunnelFailures

      private boolean hasRecentTunnelFailures(PeerProfile profile)
      Check if a peer has had recent tunnel failures (failed tests or builds). Used to gate fast/high-cap tier admission when tier counts are healthy. Requires more than 15 failures in the last hour AND a failure ratio above 30% of agreed + rejected + failed — peers with 1-15 transient failures during a network blip stay in tiers rather than being purged. This prevents tier collapse during brief network-wide events where many peers experience isolated failures.
      Parameters:
      profile - the peer's profile; a peer with no tunnel history, or no failure rate for the last hour, has nothing to report
      Returns:
      true if the peer has sustained recent tunnel failures
    • hasHighLoss

      private boolean hasHighLoss(PeerProfile profile, long now)
      True if the peer's decayed loss score is at or above PROP_LOSSY_THRESHOLD. The score decays continuously by 50% every 15 minutes since the last transport report, so a transient congestion episode clears the gate in tens of minutes while a peer that keeps reporting loss stays gated. Each report re-inflates the score. The decay, not a freshness window, provides the slow forgiveness.
      Parameters:
      profile - profile carrying the decayed loss score to test against the threshold
      now - current time in ms
      Returns:
      true if the decayed score is above zero and at or above the lossy threshold
    • inLossProbation

      private boolean inLossProbation(PeerProfile profile, long now)
      True if the peer is in loss probation: it was demoted from the fast/ high-cap tiers for packet loss and has not yet produced fresh clean evidence of recovery (see shouldReadmitLossy(PeerProfile, long)). Also covers peers whose decayed score is at or above the demotion threshold even if the demotion flag was never set, e.g. a profile loaded from disk.
      Parameters:
      profile - profile whose loss demotion flag and loss score are checked
      now - current time in ms
      Returns:
      true if the loss score is at the demotion threshold, or the peer is flagged lossy and has not yet shown fresh clean recovery
    • shouldReadmitLossy

      private boolean shouldReadmitLossy(PeerProfile profile, long now)
      True if a demoted peer may be re-admitted to the tiers: at least LOSS_READMIT_MIN_AGE has passed since the demotion AND the transport has reported a loss ratio below the demotion threshold within PROP_LOSSY_WINDOW. A stale or absent ratio is not evidence of recovery. This is what breaks the avoid-then-forget loop, because a peer that recovered organically accumulates new packets (netDb traffic continues while it is demoted) and reports a fresh clean ratio, while a peer that is still lossy never does.

      Fast-track: peers with a fresh clean loss report (loss ratio below 5% within the lossy freshness window, default 10 min) are readmitted immediately regardless of the minimum age, accelerating tier recovery after brief congestion episodes. While either tier is under target the fast-track widens to any fresh below-threshold report.

      Parameters:
      profile - profile holding the demotion timestamp and the last reported loss ratio
      now - current time in ms
      Returns:
      true if the peer may be re-admitted
    • lossyReadmitAllowed

      static boolean lossyReadmitAllowed(float lossRatio, long ratioLastUpdate, long lossySince, long now, long freshWindowMs, long minAgeMs, float threshold, float fastTrackRatio, boolean tiersShort)
      Pure decision behind shouldReadmitLossy(PeerProfile, long), pinned by tests. A peer never demoted is always allowed. Otherwise the transport must have reported within the freshness window a ratio below the demotion threshold; that fresh-clean evidence admits immediately when the ratio is near-spotless (fastTrackRatio) or when the tiers are short (tiersShort) — a short tier cannot afford to hold a peer out for a clean-but-not-spotless measurement — and otherwise only after the minimum age has passed. A stale or absent report is never evidence of recovery.
      Parameters:
      lossRatio - last loss ratio reported by the transport, 0.0 = healthy
      ratioLastUpdate - when that ratio was reported, in ms
      lossySince - when the peer was demoted for loss, 0 if never
      now - current time in ms
      freshWindowMs - how old the last report may be and still count as fresh
      minAgeMs - minimum time since demotion before a not-spotless report admits
      threshold - the lossy demotion threshold the ratio must fall below
      fastTrackRatio - ratio below which the minimum age is skipped
      tiersShort - whether either tier is under target, widening the fast track
      Returns:
      true if the peer may be re-admitted
    • clearLossIfReadmitted

      private void clearLossIfReadmitted(PeerProfile profile)
      Clear the loss demotion flag once a peer has been re-admitted, so the cleared state is persisted and re-admission is not re-evaluated on every reorganize. Must be called with the write lock held.
      Parameters:
      profile - profile whose lossy flag is cleared, so re-admission is not re-checked every cycle
    • demoteIfLossy

      public void demoteIfLossy(Hash peer)
      Instantly remove a peer from the fast/high-capacity tiers when its decayed loss score exceeds the threshold, and mark it for loss probation. Called from the transport path via ProfileManagerImpl so a lossy peer is demoted as soon as its retransmit ratio crosses the bar, instead of waiting for the next reorganize cycle. Non-blocking: if a reorganize currently holds the write lock, the reorganize purge catches the peer instead.
      Parameters:
      peer - hash of the peer to demote and place into loss probation
    • getModerateLossyThreshold

      public static float getModerateLossyThreshold(RouterContext ctx)
      The moderate de-prioritization threshold in effect, read straight from config (there is no Tuner override, unlike getLossyThreshold(RouterContext)). Separate so a caller walking a whole candidate list can resolve both thresholds once instead of paying two config lookups per candidate.
      Parameters:
      ctx - the router context
      Returns:
      the loss score at or above which a peer is de-prioritized at selection
    • isModeratelyLossy

      static boolean isModeratelyLossy(float score, float moderateThreshold, float demoteThreshold)
      Whether a decayed loss score falls in the moderate band: at or above the moderate threshold but below the demotion threshold. Such peers remain eligible for the tiers. They never crossed the hard bar — but are de-prioritized at selection time (see LOSSY_SELECTION_PENALTY).

      Pure decision — the score and both thresholds are passed in so a caller scanning a candidate list resolves them once, safe for unit tests.

      Parameters:
      score - the peer's decayed loss score
      moderateThreshold - lower bound of the band, inclusive
      demoteThreshold - upper bound of the band, exclusive
      Returns:
      whether the score is in the moderate band
    • getAverageLossRatio

      public float getAverageLossRatio()
      Average loss ratio across the fast and high-capacity tiers with a fresh measurement, as a fraction (0.0 - 1.0). Peers whose loss data is older than PROP_LOSSY_WINDOW are skipped; 0.0 if no fresh data. The tiers are the baseline because the threshold governs demotion FROM them: as lossy peers are evicted the average drops and the bar tightens. Used by the Tuner to adapt PROP_LOSSY_THRESHOLD to the network.
      Returns:
      the mean loss ratio as a fraction of 1.0, or 0.0 if no fresh data
    • getFastAverageLossRatio

      public float getFastAverageLossRatio()
      Average loss ratio across the fast tier with a fresh measurement, as a fraction (0.0 - 1.0); 0.0 if none. See getAverageLossRatio().
      Returns:
      the mean fast-tier loss ratio as a fraction of 1.0, or 0.0 if no fresh data
    • getHighCapAverageLossRatio

      public float getHighCapAverageLossRatio()
      Average loss ratio across the high-capacity tier with a fresh measurement, as a fraction (0.0 - 1.0); 0.0 if none. See getAverageLossRatio().
      Returns:
      the mean high-capacity-tier loss ratio as a fraction of 1.0, or 0.0 if no fresh data
    • getMedianLossRatio

      public float getMedianLossRatio()
      Median loss ratio of the union of the fast and high-capacity tiers with a fresh measurement, as a fraction (0.0 - 1.0). 0.0 if none. The median is robust to the worst peers dragging the mean up, so the auto-tuned demotion threshold tracks the typical tier member. See getAverageLossRatio().
      Returns:
      the median tier loss ratio as a fraction of 1.0, or 0.0 if no fresh data
    • averageLossRatio

      private float averageLossRatio(Map<Hash, PeerProfile>... tiers)
      Mean loss ratio of the union of the given tiers, restricted to profiles with a measurement fresh within PROP_LOSSY_WINDOW.
      Parameters:
      tiers - the tier maps to average over
      Returns:
      the mean as a fraction, 0.0 if no fresh data
    • medianLossRatio

      private float medianLossRatio(Map<Hash, PeerProfile>... tiers)
      Median loss ratio of the union of the given tiers, restricted to profiles with a measurement fresh within PROP_LOSSY_WINDOW.
      Parameters:
      tiers - the tier maps to average over
      Returns:
      the median as a fraction, 0.0 if no fresh data
    • freshLossRatios

      private List<Float> freshLossRatios(Map<Hash, PeerProfile>... tiers)
      Fresh loss ratios (positive and within PROP_LOSSY_WINDOW) of the union of the given tiers, sorted ascending; empty if none.
      Parameters:
      tiers - the tier maps to sample from
      Returns:
      the sorted fresh ratios
    • defaultTierSize

      static int defaultTierSize(int known, int base, int divisor)
      Fallback tier size for a threshold that scales with network size.

      Networks at or below SCALING_THRESHOLD known routers use the configured base; above it the base scales with a fraction of the network.

      Parameters:
      known - routers in the network database
      base - configured size to use, and the floor once scaling applies
      divisor - network size is divided by this once scaling applies
      Returns:
      the size to use unless a property overrides it
    • fastActiveCap

      static int fastActiveCap(int active, int base)
      Ceiling on the fast tier from the number of reachable peers.

      1.5x headroom over active for near-active peers, never below base.

      Parameters:
      active - peers reachable over any transport
      base - floor to keep even when very few peers are reachable
      Returns:
      the fast tier ceiling
    • highCapActiveCap

      static int highCapActiveCap(int active, int base)
      Ceiling on the high-capacity tier from the number of reachable peers.

      One per reachable peer, never below base.

      Was active * 2. That asks for more members than there are peers to put in them: at 1221 active peers it set a target of 2442 for a tier that tops out around 700, so the target could never be met, the early return in promoteToFillTiers() could never fire, and every demotion walked the whole capacity-ordered profile set looking for peers to seat in a tier that had already run out of candidates. A high-capacity member that is not reachable is not reservoir depth, it is a slot that cannot be used.

      Parameters:
      active - peers reachable over any transport
      base - floor to keep even when very few peers are reachable
      Returns:
      the high-capacity tier ceiling
    • getMinimumFastPeers

      protected int getMinimumFastPeers()
      Minimum number of peers to keep in the fast tier.
      Returns:
      the minimum fast peers
    • getMinimumFastPeers

      protected int getMinimumFastPeers(int known)
      Variant taking the network size, for callers that already know it.

      getKnownRouters() walks every entry in the network database, which is far too expensive to repeat once per candidate.

      Parameters:
      known - routers in the network database
      Returns:
      the minimum fast peers
    • getMaximumFastPeers

      protected int getMaximumFastPeers()
      Maximum number of peers to keep in the fast tier, capped by active peers.
      Returns:
      the maximum fast peers
    • getMaximumFastPeers

      protected int getMaximumFastPeers(int known, int active)
      Variant taking both expensive inputs, for callers that already have them.
      Parameters:
      known - routers in the network database
      active - peers currently reachable over any transport
      Returns:
      the maximum fast peers
    • getFastPeerCount

      public int getFastPeerCount()
      Current number of peers in the fast tier. Used by selectors to decide cross-pool diversity mode.
      Returns:
      fast tier size
    • getHighCapPeerCount

      public int getHighCapPeerCount()
      Current high-capacity tier population. Paired with getFastPeerCount() so first-hop selection can report the pool it drew from: a 256-candidate sample means something very different against a 200-peer tier (the whole pool) than a 1500-peer one (a fraction), and the two call for different responses.
      Returns:
      the number of peers in the high-capacity tier
    • getEffectiveCandidateSample

      public int getEffectiveCandidateSample(RouterContext ctx)
      The candidate-sample floor currently in effect, for logging.
      Parameters:
      ctx - the router context
      Returns:
      the floor passed to maxCandidateSample(int, int)
    • getMaximumHighCapPeers

      protected int getMaximumHighCapPeers()
      Maximum number of peers to keep in the high-capacity tier, capped by active peers.
      Returns:
      the maximum high cap peers
    • getMaximumHighCapPeers

      protected int getMaximumHighCapPeers(int known, int active)
      Variant taking both expensive inputs, for callers that already have them.
      Parameters:
      known - routers in the network database
      active - peers currently reachable over any transport
      Returns:
      the maximum high cap peers
    • getMinimumHighCapacityPeers

      protected int getMinimumHighCapacityPeers()
      Minimum number of peers to keep in the high-capacity tier.
      Returns:
      the minimum high capacity peers
    • getMinimumHighCapacityPeers

      protected int getMinimumHighCapacityPeers(int known)
      Variant taking the network size, for callers that already know it.
      Parameters:
      known - routers in the network database
      Returns:
      the minimum high capacity peers
    • num

      private static final String num(double num)
      Formats one number for the statistics display.
      Parameters:
      num - the value to format
      Returns:
      the formatted text
    • main

      public static void main(String[] args)
      Command-line entry point; reads profile dump files and prints thresholds.
      Parameters:
      args - paths to gzipped profile dump files
    • neutraliseUnknownBuildSuccess

      static double neutraliseUnknownBuildSuccess(double ratio)
      Maps an unknown build-success reading onto a neutral 1.0.

      An absent stat, or a window in which nothing has settled yet, is not evidence of failure. Reading it as anything else would trip isLowBuildSuccess() and switch off profile eviction, lengthen ghost cooldowns and put the first-hop selector into its defensive path for no reason - so "unknown" has to arrive upstream of every one of those as 1.0.

      Parameters:
      ratio - the raw ratio, or NaN when unknown
      Returns:
      ratio, or 1.0 when it is NaN
    • getTunnelBuildSuccess

      public double getTunnelBuildSuccess()
      Tunnel build success ratio across the whole router, cached for BUILD_SUCCESS_CACHE_MS because a single lookup costs several rate reads. 1.0 (neutral) when the window is absent or empty.
      Returns:
      the build success ratio as a fraction of 1.0
    • isLowBuildSuccess

      public boolean isLowBuildSuccess()
      Whether recent tunnel build success is below the attack threshold.
      Returns:
      whether low build success
    • writeProfile

      public void writeProfile(PeerProfile profile)
      Persist the given profile to disk.
      Parameters:
      profile - profile to write
    • getAverageLowLatencyRTT

      public float getAverageLowLatencyRTT()
      Calibrated latency floor across fast peers with data, computed during each reorganize() as the mean of the fastest samples rather than of the whole population.

      This is a second bound on the low-latency judgement, not a second judgement: a peer must satisfy both the per-transport calibrated floor and this cohort floor. It is measured from peer-test response times, a different quantity from the direct-link RTT behind the per-transport floor, so the two are never averaged together and this one cannot stand in for the other.

      Returns:
      the cohort floor in ms, 0 when fewer than ProtocolRttBaseline.SAMPLES_FOR_BASELINE peers have data
    • computeAverageLowLatencyRTT

      private float computeAverageLowLatencyRTT()
      Compute average peer test response time across fast peers with data. Pure decision — no write lock required; reads the already-rebuilt _fastPeers.
      Returns:
      the average RTT in ms, 0 if fewer than ProtocolRttBaseline.SAMPLES_FOR_BASELINE peers have data