Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) classReusable event to send an ACK for a duplicate packet after a short delay.private classActivity timer.(package private) classNotify waiters to reschedule the connection event.private classDisconnect scheduled event.private classPeriodic evaluation of the throughput floor.private classInner class for paced packet transmission.(package private) classTimer event that resends a single outbound packet when it fires and then offers itself back to_resendEventPoolfor reuse.(package private) classA single retransmit timer for all packets.(package private) static enumVerdict of one floor evaluation.private classTail Loss Probe: fire at ~2*RTT to detect loss before the RTO timer.private static final classOnegetWindowCeiling()cache entry: ceiling + sample time as a single atomically-published value (see the field's Javadoc).Field Summary
Modifier and TypeFieldDescriptionprivate final Connection.AckDupEventAck dup event.private final List<PacketLocal> Acked list, reusable for ackPackets() — avoids per-call allocation.private final AtomicLongPackets acked by the peer.private booleanFlag set when ackPackets() recorded forward progress since the last RetransmitEvent.fire.private final AtomicBooleanAck since congestion.private final AtomicIntegerHow many messages have been resent and not yet ACKed.private final Connection.ActivityTimerActivity timer.private final BandwidthEstimatorBandwidth estimator.(package private) longFixed-point congestion-avoidance credit for the deterministic growth ratchet inConnectionPacketHandler.adjustWindow.private intSample counters for the choke-size stats (one per call site).private intCounts completed releases, driving thestream.chokeSizeEndsample.private final SchedulerChooserSelects the appropriate scheduler for packet pacing.private final AtomicLongTimestamp CLOSE was received, or 0.private final AtomicLongTimestamp CLOSE was sent, or 0.private longCongestion window end.private final AtomicBooleanWhether the connection is currently established and usable.private StringError cause if the connection could not be established.private final Connection.ConEventConnection event.private final ConnectionManagerManager that created and tracks this connection.private final ObjectNotify this on connection (or connection failure).private final I2PAppContextRouter context for accessing services and configuration.private final longWhen the connection was created.private final AtomicLongTimestamp a disconnect was scheduled at, or 0.private intDuplicate ACK tracking for fast retransmit (accessed inside _outboundPacketsLock).private static final AtomicLongDuplicate-close notifications seen since startup.private static final AtomicLongNotifications suppressed since the last emitted report.private booleanWhether the established-connection resume WARN was already logged (once per connection) when a data packet exceeded its retransmit budget.private final AtomicBooleanWhether the final disconnect sequence has been initiated.private doubleEWMA of achieved goodput in bytes/sec, 0 until the floor arms.private intConsecutive sample windows that fell below the floor.private final Connection.FloorCheckEventPeriodic floor evaluation.private longBytes counted toward the sample open at_floorSampleStart.private intCompleted goodput samples backing_floorBaselineBps.private longClock value at which the current goodput sample opened.private final ConnectionPacketHandlerPacket handler for this connection.private booleanWhether the connection has been hard-disconnected (unrecoverable).private final AtomicLongHighest throughput byte acked so far, -1 when none.private booleanWhether an immediate retransmit-timer fire has already been granted for the current overdue head-of-line packet.private final MessageInputStreamStream for receiving incoming data from the remote peer.private booleanWhether the other side has choked us.private booleanWhether we are choking the other side.private final booleanWhether this is an inbound connection.private longWhen we last sent a choke ACK, for persist-timer rate-limiting of re-asserts.private longLast congestion highest unacked.private longTimestamp of the last duplicate ACK.private static final AtomicLongWhen the last report was emitted.private longWall-clock ms of the most recent loss strike, for strike decay.private longLast packet send time.private longWhen we last logged the persist-timer auto-unchoke WARN, to rate-limit it against a persistently choked peer.private longClock value of the most recent observed progress, for stall detection.private longLast received on.private final AtomicLongID of the last message sent.private longTimestamp of the last packet sent, in context clock ms.private longWall-clock time (ms) of the last permitted immediate retransmit scheduled fromscheduleSoftFailureRetransmit().private Connection.StallStateLast reported verdict, so the caller can log a rising edge only.private longWall-clock ms of the last SYN-ACK (initial or re-send) we sent for this connection.private final AtomicLongLifetime bytes received, for stats.private final AtomicLongLifetime bytes sent, for stats.private final AtomicLongLifetime duplicate bytes sent, for stats.private final AtomicLongLifetime duplicate messages received, for stats.private final AtomicLongLifetime duplicate messages sent, for stats.private final intLocal port.private final LogLogger for this connection.private intConsecutive loss events since the last recovery (graduated backoff).private booleanSet when a soft failure triggers an immediate retransmit pass, so the next send throughPacketQueue.enqueue(PacketLocal)usesSendMessageOptions.setFreshConnection(boolean)to rotate to a different outbound tunnel instead of retrying the same (possibly sick) tunnel that caused the stall.private final ObjectLocking for _nextSendTime.private longLocking: _nextSendLock.private ConnectionOptionsConnection options.private TreeMap<Long, PacketLocal> Packet ID (Long) to PacketLocal for sent but unacked packets.private final ObjectMonitor for_outboundPacketsand the sender window.private final PacketQueueOutbound queue.private final MessageOutputStreamStream for sending outgoing data to the remote peer.private final Connection.PacedPacketEventPaced event.private LinkedList<PacketLocal> Paced-send queue, lazy like_outboundPackets: null until the first paced packet, so connections that never pace allocate nothing.private final ObjectMonitor for_pacedQueue.private final ObjectPacing lock.private longPacing rate in bytes per second, for smooth transmission.private intWindow size saved before the remote choked us, restored on unchoke.private final intRandom wait.private final ConnectionDataReceiverData receiver for outbound packets.private final AtomicLongNext inbound stream ID to assign.private DestinationDestination of the remote peer, or null if not yet established.private final intRemote port.private final ConcurrentLinkedQueue<Connection.ResendPacketEvent> Pool of reusable ResendPacketEvent instances for fast retransmit.private final AtomicBooleanWhether a RESET packet has been received from the remote peer.private final AtomicLongTimestamp when a RESET was received, or 0 if none.private final AtomicLongTimestamp when a RESET was sent, or 0 if none.private intCount of consecutive retransmit timer firings without ACK progress (highestAckedThrough unchanged).private final Connection.RetransmitEventRetransmit event.private booleanWhether the rising edge of a retransmit stall has already been logged for this episode.private longClock value at which the current run of retransmit firings without forward ACK progress began, or 0 when there is no such run.private final AtomicLongNext outbound stream ID to assign.private final I2PSessionI2P session used to send and receive messages.private final AtomicBooleanSlot-release claim: CAS'd exactly once when this connection's budget slot is given back (reservation token consumed, or peer fallback when no token was bound), and re-armed only when a new generation binds a fresh token (assignReceiveStreamId).private I2PSocketFullI2P socket for this connection.private booleanSet when a soft failure triggers an immediate retransmit pass, so the resend loop can tag those resends as soft (not consuming the hard retransmit budget).private intSlow start threshold.private intCount of SYN-ACK re-sends triggered by retransmitted SYNs for this connection.private booleanWhether the regenerated SYN-ACK WARN has been logged for this connection (rate limit).private final SimpleTimer2Retransmission timer.private final Connection.TLProbeEventTlp event.private SigningPublicKeyTransient signing key for this connection, or null if not using offline keys.private final AtomicIntegerUnacknowledged packets counter.private final AtomicIntegerUnchoke messages pending.private booleanWhether share options have been updated.private Connection.WindowCeilingSampleLastgetWindowCeiling()sample, ceiling and timestamp as ONE value: two separate volatiles let a reader pair one writer's ceiling with the other writer's newer (or its own later) timestamp, serving a stale ceiling for another BDP_CACHE_MS or inverting the age check.static final intAbsolute ceiling on in-flight packets regardless of BDP estimate.private static final longRecompute the per-stream window ceiling at most this often.(package private) static final longMinimum connect window (ms) after Tuner-multiplier scaling, so a fast network never cuts a handshake below a full SYN retransmit cycle.private static intConnect timeout multiplier (50-200, 100=1.0x), set by the Tuner based on observed RTT.static final intDefault base connect timeout in milliseconds (30 seconds), the value used for an outbound connect that has none configured.private static final longInterval between "Duplicate CLOSE sent" reports.(package private) static final StringAccurate error set when the remote closes an outbound connection before it was established.(package private) static final StringAccurate error reported bywaitForConnect(int)on its own timeout.(package private) static final StringAccurate error set when an established data packet exceeds its retransmit limit.(package private) static final StringAccurate error set when an outbound SYN is never acknowledged within its retransmit budget.(package private) static final intIf we have been explicitly NACKed three times, retransmit the packet even if there are other packets in flight.private static final doubleAbsolute floor in bytes/sec, capping the proportional floor so a very small baseline is not held to a bar it can never clear.private static final intWeight of a new goodput sample in the baseline EWMA, scaled by 100.private static final intSamples required before the floor arms.private static final doubleFraction of the baseline below which the link counts as throttled.private static final intConsecutive sub-floor windows tolerated before the baseline is re-established from the observed rate.private static final longRolling goodput sample window.(package private) static final intNumber of retransmits sent directly per RTO fire before remaining retransmits are paced, to avoid flooding the I2CP queue with a single-timer-fire burst.(package private) static final intOnce a packet has been transmitted this many times, always send it directly instead of pacing it: a repeatedly-resent packet is recovery-critical and must not starve behind a stale pacing rate in the paced queue (hard drain deadline).(package private) static final intThe longest min-RTT believed to be a measurement.static final intDefault window size cap used when no per-connection or global override is set.private static intMaximum number of packets to retransmit when the timer hits.private static intGive up resending an unacked SYN after this many sends.private static intWindow ceiling floor shared by every stream, in messages; the per-stream ceiling builds up from this before any BDP term.private static final intMinimum slow start threshold after fast retransmit.(package private) static final intWindow size below which pacing is bypassed to avoid the stale-initial-RTT throttle.(package private) static final intFloor for the remote-silence bound (remoteSilentTooLong(long, int, long)) when no positive inactivity window is configured: the protocol's default inactivity window (120s).private static final longHow long the retransmit timer must go without forward ACK progress before the connection is called stalled.(package private) static final intMinimum RTT (ms) used when deriving the bandwidth slow-start threshold anchor.private static final intMultiplier for the Westwood BDP estimate used to calculate the new slow start threshold.private static final longGrace with no bytes at all before reporting a hard stall.(package private) static final intMaximum number of SYN-ACK re-sends to an existing connection in response to retransmitted SYNs, per connection lifetime.(package private) static final longMinimum spacing (ms) between SYN-ACK re-sends to an existing connection in response to retransmitted SYNs.private static final intHeadroom factor applied to a measured path RTT to derive the SYN retransmit interval.(package private) static final intMinimum SYN retransmit interval (ms) ever used once positive RTT evidence for the path exists.private static final intRecord every Nth choke-size stat sample.private static final intNumber of remaining unchoke assertions to send.private static final intHeadroom (%) applied to the BDP estimate for the per-stream ceiling.Constructor Summary
ConstructorsConstructorDescriptionConnection(I2PAppContext ctx, ConnectionManager manager, I2PSession session, SchedulerChooser chooser, SimpleTimer2 timer, PacketQueue queue, ConnectionPacketHandler handler, ConnectionOptions opts, boolean isInbound) Constructor for this connection.Method Summary
Modifier and TypeMethodDescription(package private) voidAck immediately.ackPackets(long ackThrough, long[] nacks) Process the acks and nacks received in a packet.(package private) static intbandwidthSsthreshAnchor(float bwEstPacketsPerMs, int minRttMs, int factor, int floor) The bandwidth-derived slow-start threshold anchor: the packets the pipe can hold, estimated as measured bandwidth times the effective RTT times the growth factor, floored at the given minimum.(package private) static booleanbudgetExhaustionClosesConnection(boolean budgetExhausted, boolean hasForwardProgress) Pure decision: should an exhausted per-packet retransmit budget close the connection?private longcalculatePacingDelay(int packetSize) Calculate delay needed for pacing based on packet size and current rate.private longcalculatePacingDelay(int packetSize, long pacingRate) Same as above, but for a rate the caller has already computed.private longCalculate pacing rate based on current congestion window and RTT.(package private) booleanClaim this connection's slot release, once per generation.(package private) voidClears the pending tunnel-rotation flag, so the next soft-failure pass does not rotate again.voidNotify that a close was received.(package private) static longcomputeConnectBase(long connectTimeoutMs, long connectDelayMs) Assemble the un-scaled base connect window (ms) for a connection's options.(package private) static longcomputeEffectiveConnectTimeout(long desiredTimeoutMs, int multiplier, long maxConnectTimeoutMs) Compute the effective connect timeout (ms) an outbound connect will wait, as used by bothwaitForConnect(int)and the SYN give-up budget ingetMaxSynSends().(package private) static intcomputeSynAckResendCap(long connectWindowMs, long spacingMs) Compute the per-connection SYN-ACK re-send cap: the number of retransmitted SYNs the client can fit within its effective connect window, given the inter-SYN interval in use.(package private) static intcomputeSynResendBudget(int configuredMax, int synIntervalMs, long connectWindowMs) Compute the effective number of SYN sends before this connection gives up, used bygetMaxSynSends()on each give-up decision.(package private) static intcomputeSynRetransmitInterval(int measuredRttMs, int initialRtoMs) Compute the inter-SYN retransmit interval for an outbound connection, evidence-gated and RTT-aware.(package private) static intcomputeWindowCeiling(int globalMax, float bwePerMs, int rttMs, int floorMsgs, int absMaxMsgs) Pure decision helper forgetWindowCeiling(): the per-stream window ceiling in messages —min(absMax, max(globalMax, floor, headroom * bwe * rtt)), falling back tomin(absMax, max(globalMax, floor))when no bandwidth sample exists yet.private voidCongestion occurred.(package private) static StringconnectDoneLine(String peer, long elapsedMs, boolean timedOut) The entry for the end of a blocking connect's wait, whether it ended with a connection or with the timeout giving up.(package private) static StringconnectWaitLine(String peer, long timeoutMs) The entry a blocking connect writes while it waits, naming how long it is prepared to wait for the handshake to finish.(package private) static intdecayLossStrikes(int strikes, long lastLossTime, long now, long smoothedRtt) Strike decay: a single loss strike is forgiven once at least one smoothed RTT (floored at 500 ms) has passed without another strike, rolling the graduated backoff back down toward the gentle tier.voiddisconnect(boolean cleanDisconnect) Must be called when we are done with this connection.voiddisconnect(boolean cleanDisconnect, boolean removeFromConMgr) Must be called when we are done with this connection.voidMust be called when we are done with this connection.(package private) static inteffectiveInactivityTimeout(int configured, int fallbackMs) Effective inactivity window for the remote-silence bound.private voidevaluateFloor(long now, double bps) Roll the baseline forward and report the verdict for one closed window.(package private) voidNotify the scheduler that an event occurred on this connection.(package private) Connection.AckDupEventSchedule an event on our timer.longHow many packets have we sent and the other side has ACKed?longTimestamp when a close was received.longTimestamp when a close was sent.longCongestion window end sequence number.Connection error message, if any.The ConnectionManager that created this connection.(package private) static intCurrent Tuner-scaled connect timeout percentage.longTimestamp when this connection was created.longTimestamp when the disconnect was scheduled.static intIdle time after which an established connection is dropped (i2p.streaming.disconnectTimeout) in ms.(package private) longThe total time (ms) from connection creation during which this connecting connection may wait for its SYN to be acknowledged before the connect path tears the handshake down.(package private) static longRouter-wide absolute cap on the effective connect window, in milliseconds.static intCeiling floor every stream ramps at least to, whatever its own BDP says.booleanCheck if this connection has been hard-disconnected (via RESET).longHighest outbound packet we have received an ack for.Input stream that the local peer receives data on.booleanAlways true at the start, even if we haven't gotten a reply on an outbound connection.longTimestamp of the last send or receive activity.longWhat was the last packet Id sent to the peer?longWhen did we last send anything to the peer?longLifetime of this connection in milliseconds.longTotal bytes received on this connection.longTotal bytes sent on this connection.longTotal duplicate bytes sent on this connection.longTotal duplicate messages received on this connection.longTotal duplicate messages sent on this connection.intLocal port of this connection.(package private) longAbsolute cap on this connection's connect window, in milliseconds.static intDefault resend delay (i2p.streaming.maxResendDelay) in ms.static intMaximum packets to retransmit per timer fire.private intMaximum number of packets to retransmit in a single timer fire.static intConfigured give-up count for an unacknowledged SYN.private intEffective SYN give-up count for this connection:maxSynResendsscaled up so the retransmit budget (count * inter-SYN interval) covers the connect window, so the retransmit timer cannot tear this connection down whilewaitForConnect(int)is still waiting.static intDefault resend delay (i2p.streaming.minResendDelay) in ms.longNext outbound packet sequence number.longTime when the scheduler next want to send a packet, or -1 if never.Retrieve the current ConnectionOptions.Output stream that the local peer sends data to the remote peer on.Packet handler for this connection.intgetPort()The remote port.private intgetPTO()Tail Loss Probe timeout (PTO).longThe stream ID of a peer connection that sends data to us, or zero if unknown.Destination of the remote peer.private StringShort label for the remote peer, for log lines.The key to verify signatures with.booleanCheck if a reset has been received on this connection.longTimestamp when a reset was received.booleanCheck if a reset has been sent on this connection.longTimestamp when a reset was sent.(package private) intReturns the current retransmit count (consecutive retransmit timer firings without ACK progress).longWhat stream do we send data to the peer on?I2P session for this connection.Socket associated with this connection.(package private) intCurrent slow-start threshold: the window at which congestion avoidance takes over from exponential slow start.private intThe fixed interval between SYN retransmits for this connection, evidence-gated.(package private) longReturns the number of milliseconds between the last send and now, for external stall detection callers.intCount of unacked packets received.intHow many packets have we sent but not yet received an ACK for?(package private) intPer-stream window ceiling: the minimum ofABSOLUTE_MAX_WINDOWand the larger of the Tuner-managed global ceiling (or the per-connection override) and this stream's own bandwidth-delay product scaled byWINDOW_CEILING_HEADROOM_PCT.(package private) static intgraduatedLossSsthresh(int strikes, int wsize, int bwBasedSsthresh) Slow-start threshold for a graduated loss response: never below the graduated window (so the connection can regrow to at least its post-cut capacity) and never below the bandwidth-derived estimate.(package private) static intgraduatedLossWindow(int strikes, int wsize) Graduated congestion response: the window is cut by an amount that scales with the number of CONSECUTIVE loss events since the last recovery, so a single failed packet does not collapse the connection while persistent loss still backs off.(package private) static booleanhardResendBudgetExceeded(int totalSends, int softResends, int maxResends) Pure decision: has the number of HARD send attempts (sends that took the router's reply as something to retry after, i.e.private booleanWhether this connection has unacknowledged data it could be stuck behind.voidincrementBytesReceived(int bytes) Increment the lifetime bytes received counter.voidincrementBytesSent(int bytes) Increment the lifetime bytes sent counter.voidincrementDupBytesSent(int bytes) Increment the lifetime duplicate bytes sent counter.voidincrementDupMessagesReceived(int msgs) Increment the lifetime duplicate messages received counter.voidincrementDupMessagesSent(int msgs) Increment the lifetime duplicate messages sent counter.voidIncrement the count of unacked packets received.(package private) static intinitialSsthresh(int maxSlowStartWindow, int globalMaxWindow, int initialWindowSize) The slow-start threshold a freshly constructed connection starts with, clamped so it can never exceed the connection's window ceiling.booleanisChoked()Whether the other side is choking us.private booleanAssert the connection is still usable, throwing instead of reporting it.booleanCheck if this is an inbound connection.(package private) booleanReturns true if the next send should use a fresh connection (tunnel rotation).(package private) booleanReturns true if the retransmit count exceeds the stall threshold, indicating a stalled download on a slow/lossy path.(package private) static booleanisRetransmitStalledNow(boolean established, long now, long stallSinceMs, long thresholdMs) Decide whether the retransmit timer has gone long enough without forward ACK progress to call it a stall.private voidCancel and remove all packets awaiting ack.private StringlocalTag()First 8 characters of the local destination's base32 hash, so a log line names which of this router's services owns the stream.(package private) static booleanlossEpisodeRecovered(boolean hadCongestionMark, long ackThrough, long lastCongestionHighestUnacked, int activeResends) Whether a congestion episode's lost window has been fully recovered.private voidPublish a stall to the router so outbound tunnel selection can prefer a different path for this destination.(package private) Connection.ResendPacketEventnewResendPacketEvent(PacketLocal packet) A new ResendPacketEvent from the pool or a fresh instance.(package private) static intnextRetransmitDelay(long now, long oldestLastSend, long rto, boolean immediateAlreadyFired) Delay before the next retransmit-timer fire.private voidnoteProgress(int ackedPackets, int messageSize) Record acknowledged progress and, when a goodput window has closed, fold its rate into the baseline and evaluate the floor.voidCalled by CPH when a CLOSE packet is sent.voidNotify that a close that we sent, and all previous packets, were acked.private TreeMap<Long, PacketLocal> Lazily allocate the sender-side unacked-window map.private intNull-safe window size.private LinkedList<PacketLocal> Lazily allocate the paced-send queue.(package private) voidCalled when a packet is received on this connection.booleanpacketSendChoke(long timeoutMs) This doesn't "send a choke".(package private) voidrecordSynAckResend(long now) Record that a SYN-ACK was sent for this connection atnow(initial re-send fromConnectionHandler#resendSynAckonly.(package private) static booleanremoteSilentTooLong(long lastReceivedOn, int inactivityTimeout, long now) Pure predicate: has the remote sent nothing at all for a full inactivity window, while unacked packets are still in flight?private voidreportStallState(Connection.StallState verdict) Emitstream.con.stallDetectedandstream.con.throttleDetected.private voidReset activity timer.voidNotify that a reset was received.(package private) voidRe-arm the slot-release claim for a new generation, called byConnectionManager.assignReceiveStreamId(Connection, Hash)when a pooled connection binds a fresh reservation token: the next teardown of this object must be able to claim again.(package private) static intrestoredWindowOnUnchoke(int saved, int ceiling) The window to restore when the remote unchokes us.(package private) static intretransmitCount(int inFlight, int maxRtx) How many packets a retransmission timeout resends: at most half the packets in flight (RFC 5681 section 4.3), never more than the retransmit cap, and at least one so a lone lost packet is not skipped.(package private) static doublesampleRateBps(long bytes, long elapsedMs) Roll the goodput sample forward, returning the rate of the window that just closed.voidscheduleConnectionEvent(long msToWait) Called fromSchedulerImpl.private booleanSchedule the end of the TIME-WAIT state, but only if not previously scheduled.(package private) voidTrigger immediate retransmission of unacked packets after a soft failure (e.g., tunnel expiry, no tunnels).(package private) voidFlush any data that we can.(package private) voidsendPacket(PacketLocal packet) This sends all 'normal' packets (acks and data) for the first time.private voidGot a packet we shouldn't have, send 'em a reset.private voidSend a tail loss probe: re-enqueue the oldest unacked packet.voidsetChoked(boolean on) Choked state set by the other side.voidsetChoking(boolean on) Choking state toward the other side.voidsetCongestionWindowEnd(long endMsg) Congestion window end sequence number.voidsetConnectionError(String err) Connection error message.(package private) static voidsetConnectTimeoutMultiplier(int pct) Set the connect timeout multiplier, clamped to [30, 200] percent.static voidsetGlobalMaxWindowSize(int val) Raise or lower the ceiling floor shared by every stream.static voidsetMaxRetransmissions(int val) Resend batch size cap, clamped to [8, 128] packets.static voidsetMaxSynResends(int val) Configured SYN give-up count, clamped to [3, 16].(package private) voidSet the flag so the next send through PacketQueue uses a fresh connection, rotating to a different outbound tunnel when the current one has stalled.voidsetNextSendTime(long when) If the next send time is currently >= 0 (i.e.voidsetOptions(ConnectionOptions opts) ConnectionOptions for this connection.voidsetReceiveStreamId(long id) Stream ID that the peer sends data on.voidsetRemotePeer(Destination peer) Remote peer of this connection, non-null.voidsetRemoteTransientSPK(SigningPublicKey transientSPK) Transient signing public key of the remote peer.voidsetSendStreamId(long id) Stream ID that we send data on.voidsetSocket(I2PSocketFull socket) Socket associated with this connection.private static booleanshouldLogDuplicateClose(long now) Rate-limit the duplicate-close report.(package private) static booleanshouldLogPersistWarn(long lastWarnTime, long now, long minIntervalMs) Whether to log another persist-timer auto-unchoke WARN.(package private) static booleanshouldLogStallEdge(boolean stallDetached, boolean alreadyLogged) Whether this firing should produce a stall warning, given the latch state.(package private) static booleanshouldPaceRetx(int burstCount, int nResends) Pure decision: should this packet's retransmit be paced through the paced queue rather than sent directly?(package private) static booleanshouldRateLimitSoftRetransmit(long now, long lastSoftFailRetransmit, long minSpacingMs) Pure decision: is this soft-failure report too close to the last permitted immediate retransmit to fire another now?(package private) booleanshouldResendSynAck(long now) Whether this connection should re-send a SYN-ACK for a retransmitted SYN right now, given its own send history.(package private) static booleanshouldResendSynAck(long lastSynAckSentOn, int resends, long now, long minSpacingMs, int maxResends) Decide whether to re-send a SYN-ACK for an existing connection in response to a retransmitted SYN.(package private) static booleanshouldSampleChokeSizeBegin(int counter) Whether a first-block iteration should emit astream.chokeSizeBeginsample.(package private) static booleanshouldSampleChokeSizeEnd(int counter) Whether a completed packet release should emit astream.chokeSizeEndsample.private booleanshouldWait(int unacked, int wsz, int windowCeiling) Whether the sender should block.(package private) static Connection.StallStatestallVerdict(long now, long lastProgressAt, double observedBps, double ratio, int samples, double baselineBps, double absFloorBps, long stallGraceMs) Decide whether a connection is delivering far below its own established rate, is fully stalled, or is healthy.(package private) static Connection.StallStatestallVerdict(long now, long lastProgressAt, double observedBps, double ratio, int samples, double baselineBps, double absFloorBps, long stallGraceMs, boolean outstandingData) Decide the stall verdict for one window, told whether anything is actually in flight to be stalled on.private StringReceive/send stream IDs as"recv/send","Unknown"when not yet assigned.(package private) static booleanstuckLifetimeExceeded(long budgetMs, long now, long createdOn) Pure predicate: has the oldest unacked packet been in flight beyond the worst-case retransmit budget, measured from CREATION?(package private) booleanWhether the "re-sending SYN-ACK" WARN has already been logged for this connection, and mark it logged on first check.(package private) static booleansynGiveUpBudgetExceeded(int totalSends, int softResends, int maxSynSends) Pure decision: has the retransmit loop exceeded the SYN give-up budget after excluding soft-failure-triggered resends?toString()Human-readable summary of this connection.(package private) static doubleupdatedBaseline(double baseline, double sampleBps, int percent) Fold a completed goodput sample into the baseline EWMA.private voidRefresh the cached pacing rate when the congestion window or RTT changes.voidUpdate shared options from the TCB cache.(package private) voidWait for the connection to be established, with the option's connectTimeout.(package private) voidwaitForConnect(int timeoutMs) Wait for the connection to be established, but no longer than timeoutMs.(package private) voidNotify all threads waiting inpacketSendChoke(long).
Field Details
_context
Router context for accessing services and configuration._log
Logger for this connection._connectionManager
Manager that created and tracks this connection._session
I2P session used to send and receive messages._remotePeer
Destination of the remote peer, or null if not yet established._transientSPK
Transient signing key for this connection, or null if not using offline keys._sendStreamId
Next outbound stream ID to assign._receiveStreamId
Next inbound stream ID to assign._lastSendTime
private volatile long _lastSendTimeTimestamp of the last packet sent, in context clock ms._lastSendId
ID of the last message sent._resetReceived
Whether a RESET packet has been received from the remote peer._resetSentOn
Timestamp when a RESET was sent, or 0 if none._resetReceivedOn
Timestamp when a RESET was received, or 0 if none._lastSynAckSentOn
private volatile long _lastSynAckSentOnWall-clock ms of the last SYN-ACK (initial or re-send) we sent for this connection._synAckResends
private volatile int _synAckResendsCount of SYN-ACK re-sends triggered by retransmitted SYNs for this connection._synAckWarnLogged
private volatile boolean _synAckWarnLoggedWhether the regenerated SYN-ACK WARN has been logged for this connection (rate limit)._connected
Whether the connection is currently established and usable._finalDisconnect
Whether the final disconnect sequence has been initiated._slotReleaseClaimed
Slot-release claim: CAS'd exactly once when this connection's budget slot is given back (reservation token consumed, or peer fallback when no token was bound), and re-armed only when a new generation binds a fresh token (assignReceiveStreamId). Lets every teardown path race to release without any of them draining a slot twice. The failure mode that lets a hard-disconnect sweep decrement a later stream's count._hardDisconnected
private volatile boolean _hardDisconnectedWhether the connection has been hard-disconnected (unrecoverable)._inputStream
Stream for receiving incoming data from the remote peer._outputStream
Stream for sending outgoing data to the remote peer._chooser
Selects the appropriate scheduler for packet pacing._nextSendTime
private long _nextSendTimeLocking: _nextSendLock._ackedPackets
Packets acked by the peer._createdOn
private final long _createdOnWhen the connection was created._closeSentOn
Timestamp CLOSE was sent, or 0._closeReceivedOn
Timestamp CLOSE was received, or 0._unackedPacketsReceived
Unacknowledged packets counter._congestionWindowEnd
private long _congestionWindowEndCongestion window end._highestAckedThrough
Highest throughput byte acked so far, -1 when none._dupAckCount
private int _dupAckCountDuplicate ACK tracking for fast retransmit (accessed inside _outboundPacketsLock)._lastDupAck
private long _lastDupAckTimestamp of the last duplicate ACK._ssthresh
private volatile int _ssthreshSlow start threshold._isInbound
private final boolean _isInboundWhether this is an inbound connection._outboundPackets
Packet ID (Long) to PacketLocal for sent but unacked packets. Lazily allocated on the first send so inbound-only connections allocate no window at all. All access (including reads) happens under_outboundPacketsLock; null until first write._outboundPacketsLock
Monitor for_outboundPacketsand the sender window. Exposed viagetWindowLock()so ConnectionPacketHandler serializes with us._outboundQueue
Outbound queue._handler
Packet handler for this connection._options
Connection options._receiver
Data receiver for outbound packets._socket
I2P socket for this connection._connectionError
Error cause if the connection could not be established._disconnectScheduledOn
Timestamp a disconnect was scheduled at, or 0._lastReceivedOn
private volatile long _lastReceivedOnLast received on. Written on the receive/notification thread (packetReceived), read by the inactivity backstop timer (remoteSilentTooLong, getLastActivityOn) — volatile for a stable reading across threads._activityTimer
Activity timer._lastCongestionHighestUnacked
private volatile long _lastCongestionHighestUnackedLast congestion highest unacked._lossStrikes
private int _lossStrikesConsecutive loss events since the last recovery (graduated backoff). Reset inackPackets(long, long[])once the lost window is fully recovered, and decayed by one tier inConnection.ResendPacketEvent.retransmit()when a fresh strike arrives more than one smoothed RTT after the previous one (seedecayLossStrikes(int, long, long, long)). Accessed only under the _outboundPacketsLock._lastLossStrikeTime
private long _lastLossStrikeTimeWall-clock ms of the most recent loss strike, for strike decay. Same lock discipline as_lossStrikes._establishedResumeWarned
private boolean _establishedResumeWarnedWhether the established-connection resume WARN was already logged (once per connection) when a data packet exceeded its retransmit budget. Reset only when the connection object is reused._immediateRtoFired
private boolean _immediateRtoFiredWhether an immediate retransmit-timer fire has already been granted for the current overdue head-of-line packet. Cleared as soon as the timer is no longer overdue, so each new overdue period gets one immediate attempt. SeenextRetransmitDelay(long, long, long, boolean)._retransmitStallLogged
private boolean _retransmitStallLoggedWhether the rising edge of a retransmit stall has already been logged for this episode. Keeps the warning to one line per stall instead of one per timer firing; cleared as soon as forward ACK progress resumes. See the retransmit timer._retransmitStallSince
private long _retransmitStallSinceClock value at which the current run of retransmit firings without forward ACK progress began, or 0 when there is no such run. The stall verdict is measured from here rather than from a count of firings, because RTO doubling makes a count mean a different duration on every connection.RETRANSMIT_STALL_MS
private static final long RETRANSMIT_STALL_MSHow long the retransmit timer must go without forward ACK progress before the connection is called stalled. MatchesSTALL_GRACE_MSso the timer-driven and the goodput-driven detectors agree on what "stalled" means rather than one firing on a condition the other considers normal.- See Also:
_caWindowAccumulator
long _caWindowAccumulatorFixed-point congestion-avoidance credit for the deterministic growth ratchet inConnectionPacketHandler.adjustWindow. Increments are derived from accumulated ACK credit instead of a per-ACK random draw, giving the same expected growth rate as the probabilistic gate without RNG noise. Holds the 20-bit fractional remainder of the accumulator. Must only be read/written while holding_outboundPacketsLock; zeroed in retransmit() when the window is cut._pacingRate
private volatile long _pacingRatePacing rate in bytes per second, for smooth transmission._lastPacketSendTime
private volatile long _lastPacketSendTimeLast packet send time._pacingLock
Pacing lock._preChokeWindowSize
private volatile int _preChokeWindowSizeWindow size saved before the remote choked us, restored on unchoke. -1 means no saved value (not choked, or choked before field existed)._isChoked
private volatile boolean _isChokedWhether the other side has choked us._isChoking
private volatile boolean _isChokingWhether we are choking the other side._lastChokeAckTime
private volatile long _lastChokeAckTimeWhen we last sent a choke ACK, for persist-timer rate-limiting of re-asserts._lastPersistWarnTime
private volatile long _lastPersistWarnTimeWhen we last logged the persist-timer auto-unchoke WARN, to rate-limit it against a persistently choked peer. 0 = never (resets on unchoke)._unchokesToSend
Unchoke messages pending._ackSinceCongestion
Ack since congestion._connectLock
Notify this on connection (or connection failure)._nextSendLock
Locking for _nextSendTime._activeResends
How many messages have been resent and not yet ACKed._softFailureResendPending
private volatile boolean _softFailureResendPendingSet when a soft failure triggers an immediate retransmit pass, so the resend loop can tag those resends as soft (not consuming the hard retransmit budget). Written on the router callback thread inscheduleSoftFailureRetransmit(), read and cleared on the timer thread inConnection.RetransmitEvent.timeReached(). Cleared on cancel so a cancelled soft pass cannot leak into a later RTO-driven pass._nextSendFreshConnection
private volatile boolean _nextSendFreshConnectionSet when a soft failure triggers an immediate retransmit pass, so the next send throughPacketQueue.enqueue(PacketLocal)usesSendMessageOptions.setFreshConnection(boolean)to rotate to a different outbound tunnel instead of retrying the same (possibly sick) tunnel that caused the stall. Only the first packet in a soft-failure pass carries the marker; subsequent packets reuse the already rotated tunnel, so the handover settles before any data flows and an in-flight download is never disturbed. Written on the router callback thread inscheduleSoftFailureRetransmit(), cleared on first use inPacketQueue.enqueue(PacketLocal)and on cancel._retransmitCount
private volatile int _retransmitCountCount of consecutive retransmit timer firings without ACK progress (highestAckedThrough unchanged). Used to detect stalled downloads on slow/lossy paths where soft-failure rotation does not trigger. Reset to 0 only when ackPackets() was called since the last retransmit fire (i.e., when the peer actually delivered ACKs). Written and read on the router callback thread._ackProgress
private volatile boolean _ackProgressFlag set when ackPackets() recorded forward progress since the last RetransmitEvent.fire. Used to decide whether to reset _retransmitCount inConnection.RetransmitEvent.timeReached(). Written and read on the router callback thread._lastSoftFailRetransmit
private volatile long _lastSoftFailRetransmitWall-clock time (ms) of the last permitted immediate retransmit scheduled fromscheduleSoftFailureRetransmit(). Gates back-to-back soft failures (e.g. NO_LEASESET while the LeaseSet fetch is in flight) so they defer to the retransmit timer instead of firing a new immediate pass at the I2CP round-trip rate, which exhausted the SYN give-up budget within ~200ms. Written on the router callback thread, read on itself._connectionEvent
Connection event._retransmitEvent
Retransmit event._pacedEvent
Paced event._pacedQueue
Paced-send queue, lazy like_outboundPackets: null until the first paced packet, so connections that never pace allocate nothing. All access happens under_pacedQueueLock._pacedQueueLock
Monitor for_pacedQueue._tlpEvent
Tlp event._ackDupEvent
Ack dup event._ackedList
Acked list, reusable for ackPackets() — avoids per-call allocation. Only accessed from the receive thread, serialized by _dataLock._randomWait
private final int _randomWaitRandom wait._localPort
private final int _localPortLocal port._remotePort
private final int _remotePortRemote port._timer
Retransmission timer._bwEstimator
Bandwidth estimator.BDP_CACHE_MS
private static final long BDP_CACHE_MSRecompute the per-stream window ceiling at most this often. The ceiling feathers at the RTT/estimator cadence anyway, so a cached value remains accurate across the several write attempts an eagerly blocked app will issue.- See Also:
WINDOW_CEILING_HEADROOM_PCT
private static final int WINDOW_CEILING_HEADROOM_PCTHeadroom (%) applied to the BDP estimate for the per-stream ceiling. The Westwood+ sample lags ACK cadence and the 500ms RTT floor under-measures fast paths, so the raw estimate would park the window just under the true pipe; loss — not the ceiling — bounds overshoot.- See Also:
_windowCeilingSample
LastgetWindowCeiling()sample, ceiling and timestamp as ONE value: two separate volatiles let a reader pair one writer's ceiling with the other writer's newer (or its own later) timestamp, serving a stale ceiling for another BDP_CACHE_MS or inverting the age check. Volatile: written from the app-writer choke loop and the ACK path.TELEMETRY_SAMPLE_PERIOD
private static final int TELEMETRY_SAMPLE_PERIODRecord every Nth choke-size stat sample. These fire per packet sent or released; sampling the aggregate (scaling the value by the period) preserves the display stats with a fraction of the Rate lock traffic.- See Also:
_chokeSizeBeginCnt
private int _chokeSizeBeginCntSample counters for the choke-size stats (one per call site). Monotonic per connection: the emission test is a threshold on the counter, so a counter that resets can only fire again once it has climbed back to the period._chokeSizeEndCnt
private int _chokeSizeEndCntCounts completed releases, driving thestream.chokeSizeEndsample.FLOOR_REBASE_WINDOWS
private static final int FLOOR_REBASE_WINDOWSConsecutive sub-floor windows tolerated before the baseline is re-established from the observed rate. Without this the baseline would hold a stale high-water mark forever; with a plain EWMA it would instead decay toward the degraded rate within a few windows and silently excuse exactly the sustained slowdown the detector exists to catch. Requiring a long run of sub-floor windows distinguishes a real capacity change from a transient dip.- See Also:
FLOOR_SAMPLE_MS
private static final long FLOOR_SAMPLE_MSRolling goodput sample window.- See Also:
FLOOR_MIN_SAMPLES
private static final int FLOOR_MIN_SAMPLESSamples required before the floor arms. A connection that starts slow must never be judged against a baseline built from its own slow start, which is the failure mode that would leave this detector permanently inert on exactly the cold-pool case it exists to catch.- See Also:
FLOOR_RATIO
private static final double FLOOR_RATIOFraction of the baseline below which the link counts as throttled.- See Also:
FLOOR_ABSOLUTE_BPS
private static final double FLOOR_ABSOLUTE_BPSAbsolute floor in bytes/sec, capping the proportional floor so a very small baseline is not held to a bar it can never clear.- See Also:
STALL_GRACE_MS
private static final long STALL_GRACE_MSGrace with no bytes at all before reporting a hard stall. Generous relative toFLOOR_SAMPLE_MSso a single quiet window does not trip it.- See Also:
FLOOR_EWMA_PERCENT
private static final int FLOOR_EWMA_PERCENTWeight of a new goodput sample in the baseline EWMA, scaled by 100.- See Also:
_floorBaselineBps
private volatile double _floorBaselineBpsEWMA of achieved goodput in bytes/sec, 0 until the floor arms.Volatile: written on the ACK path and read by
Connection.FloorCheckEventon the timer thread. A non-volatile double is not required to be read atomically, so the timer could see a half-written value and call a live connection stalled, or vice versa._floorSamples
private volatile int _floorSamplesCompleted goodput samples backing_floorBaselineBps. Volatile for the same cross-thread reason._floorSampleBytes
private volatile long _floorSampleBytesBytes counted toward the sample open at_floorSampleStart. Only the ACK path touches this one; it is volatile with the rest of the group for cross-thread visibility of each field on its own, not because volatile keeps the pair consistent - the timer can still read a byte count from one sample window against a start time from another._floorSampleStart
private volatile long _floorSampleStartClock value at which the current goodput sample opened. Volatile for the same cross-thread reason._lastProgressAt
private volatile long _lastProgressAtClock value of the most recent observed progress, for stall detection. Volatile: the ACK path advances it and the timer thread reads it to decide whether the connection has gone quiet._floorBelowCount
private volatile int _floorBelowCountConsecutive sample windows that fell below the floor. Reset by any at-or-above-floor window. Volatile for the same cross-thread reason._floorCheckEvent
Periodic floor evaluation. Armed only once a baseline exists, so a connection that never transfers data does not pay for a timer, and it is what makes a hard stall observable at all: the ACK path can only evaluate the floor when bytes are being acknowledged, so a connection that has gone entirely quiet would otherwise never be examined._lastStallState
Last reported verdict, so the caller can log a rising edge only. Volatile: both the ACK path andConnection.FloorCheckEventreport verdicts, and each has to see the other's to avoid logging the same edge twice._lifetimeBytesSent
Lifetime bytes sent, for stats._lifetimeBytesReceived
Lifetime bytes received, for stats._lifetimeDupMessageSent
Lifetime duplicate messages sent, for stats._lifetimeDupMessageReceived
Lifetime duplicate messages received, for stats._lifetimeDupBytesSent
Lifetime duplicate bytes sent, for stats.DEFAULT_CONNECT_TIMEOUT
public static final int DEFAULT_CONNECT_TIMEOUTDefault base connect timeout in milliseconds (30 seconds), the value used for an outbound connect that has none configured. It is not the effective connect window: that is this value (plus the connect delay on the delayed-SYN path) scaled bygetConnectTimeoutMultiplier()and capped atgetMaxConnectTimeout().- See Also:
connectTimeoutMultiplier
private static volatile int connectTimeoutMultiplierConnect timeout multiplier (50-200, 100=1.0x), set by the Tuner based on observed RTT. Scales each client's desired timeout so fast networks fail fast and slow networks get enough time. Applied in waitForConnect().MAX_WINDOW_SIZE_DEFAULT
public static final int MAX_WINDOW_SIZE_DEFAULTDefault window size cap used when no per-connection or global override is set. This global is the guaranteed floor of the per-stream ceiling (getWindowCeiling()), which the Tuner adjusts based on observed RTT, bandwidth, loss, and memory pressure — every stream ramps at least to this value, while its own BDP may lift the ceiling towardABSOLUTE_MAX_WINDOWso a fast stream is not held back by signals gathered from other streams. Raised from 1024 to 2048 to better utilize high-BDP paths.ABSOLUTE_MAX_WINDOW
public static final int ABSOLUTE_MAX_WINDOWAbsolute ceiling on in-flight packets regardless of BDP estimate. Prevents runaway window growth from estimator noise or bugs. Set to accommodate high-BDP paths (e.g. 50Mbps @ 1s RTT ~ 6250 packets @ 1KB). Raised from 4096 to 8192 for high-throughput bulk transfers.- See Also:
maxWindowSize
private static volatile int maxWindowSizeWindow ceiling floor shared by every stream, in messages; the per-stream ceiling builds up from this before any BDP term.UNCHOKES_TO_SEND
private static final int UNCHOKES_TO_SENDNumber of remaining unchoke assertions to send.- See Also:
SSTHR_BW_FACTOR
private static final int SSTHR_BW_FACTORMultiplier for the Westwood BDP estimate used to calculate the new slow start threshold.- See Also:
MIN_SSTHR_FAST_RETX
private static final int MIN_SSTHR_FAST_RETXMinimum slow start threshold after fast retransmit.- See Also:
SS_THRESH_BW_ANCHOR_MIN_RTT
static final int SS_THRESH_BW_ANCHOR_MIN_RTTMinimum RTT (ms) used when deriving the bandwidth slow-start threshold anchor. A raw sub-second min-RTT would shrink the anchor toward the degenerate 16-packet floor after the first loss event, locking a healthy pipe at ~10KB/s.- See Also:
MAX_PLAUSIBLE_MIN_RTT_MS
static final int MAX_PLAUSIBLE_MIN_RTT_MSThe longest min-RTT believed to be a measurement. Anything at or above it is treated as the never-measured sentinel rather than a reading: a real minimum round trip inside the network is seconds at the very most, and the sentinel is 24 days. Without the distinction the bandwidth anchor is computed against the sentinel and the result is not a bandwidth estimate at all.- See Also:
maxSynResends
private static volatile int maxSynResendsGive up resending an unacked SYN after this many sends. SYN retransmission uses a fixed RTO interval (no backoff — there is no congestion to manage before the connection is established), so the total SYN budget equals maxSynResends * interval. The interval is the evidence-gated RTT-aware value fromcomputeSynRetransmitInterval(int, int)— the initial RTO (default 9s) before any RTT measurement, or an interval derived from the measured path RTT once evidence exists. With the default 9s interval and 12 sends the fallback budget is 108s, safely past the 75s connect-window cap on a no-evidence dead path.This is the per-connection floor for the connect path:
getMaxSynSends()raises the effective give-up count as needed so the retransmit budget never expires before the connect window (computeEffectiveConnectTimeout(long, int, long)) does. Otherwise the retransmit timer can tear the connection down error-free whilewaitForConnect(int)is still waiting, leaving the caller with a generic "Connection failed" instead of an accurate message.CONNECT_TIMEOUT_FLOOR_MS
static final long CONNECT_TIMEOUT_FLOOR_MSMinimum connect window (ms) after Tuner-multiplier scaling, so a fast network never cuts a handshake below a full SYN retransmit cycle.- See Also:
ERR_SYN_NOT_ACKNOWLEDGED
Accurate error set when an outbound SYN is never acknowledged within its retransmit budget.- See Also:
ERR_RETRANSMIT_LIMIT
Accurate error set when an established data packet exceeds its retransmit limit.- See Also:
ERR_CONNECTION_REFUSED
Accurate error set when the remote closes an outbound connection before it was established.- See Also:
ERR_CONNECTION_TIMED_OUT
Accurate error reported bywaitForConnect(int)on its own timeout.- See Also:
SYN_RTO_MIN
static final int SYN_RTO_MINMinimum SYN retransmit interval (ms) ever used once positive RTT evidence for the path exists. Keeps a fresh SYN from re-firing faster than the fabric can plausibly round-trip even on a very fast tunnel, avoiding a retransmit storm on a peer that is merely slow to complete the handshake.- See Also:
SYN_RTO_HEADROOM_PCT
private static final int SYN_RTO_HEADROOM_PCTHeadroom factor applied to a measured path RTT to derive the SYN retransmit interval. A single RTT sample is a lower bound on the true round-trip under a congested tunnel, so scale it up (as a percentage, e.g. 150 = 1.5x) to give the SYN and its ACK room to complete without a spurious retransmit.- See Also:
SYN_ACK_RESEND_MIN_SPACING_MS
static final long SYN_ACK_RESEND_MIN_SPACING_MSMinimum spacing (ms) between SYN-ACK re-sends to an existing connection in response to retransmitted SYNs.This is the floor: the live spacing used by
shouldResendSynAck(long)is the connection's owngetSynRetransmitInterval()— the same RTT-aware interval the client uses for its SYN retransmit timer — so every client retransmit can receive a SYN-ACK response even as the Tuner adapts the initial RTO. At 3s the server skipped every other retransmit, starving lossy I2P paths where the initial SYN-ACK was lost and each re-send must traverse a degraded tunnel.The amplification bound is preserved by the resend cap (
computeSynAckResendCap(long, long)): spacing ensures one answer per RTO, the count cap hard-bounds the total.- See Also:
SYN_ACK_RESEND_MAX
static final int SYN_ACK_RESEND_MAXMaximum number of SYN-ACK re-sends to an existing connection in response to retransmitted SYNs, per connection lifetime.Used when no connection window is configured (connect timeout absent). The live cap is
computeSynAckResendCap(long, long), derived from the client's effective connect window so a connection that spins in a half-open state (client stuck retransmitting at its RTO, handshake never completes) cannot mint SYN-ACKs past the point the client itself has given up.- See Also:
maxRetransmissions
private static volatile int maxRetransmissionsMaximum number of packets to retransmit when the timer hits. Default 32 allows faster loss recovery on wide windows (256-packet window backlog clears in 4 rounds instead of 8).REMOTE_SILENT_FALLBACK_MS
static final int REMOTE_SILENT_FALLBACK_MSFloor for the remote-silence bound (remoteSilentTooLong(long, int, long)) when no positive inactivity window is configured: the protocol's default inactivity window (120s). A connection must never be held past this on a dead path just because an options object carries a non-positive timeout.- See Also:
DUPLICATE_CLOSE_LOG_INTERVAL_MS
private static final long DUPLICATE_CLOSE_LOG_INTERVAL_MSInterval between "Duplicate CLOSE sent" reports.- See Also:
_duplicateCloseSent
Duplicate-close notifications seen since startup._duplicateCloseSuppressed
Notifications suppressed since the last emitted report._lastDuplicateCloseLog
When the last report was emitted.FAST_RETRANSMIT_THRESHOLD
static final int FAST_RETRANSMIT_THRESHOLDIf we have been explicitly NACKed three times, retransmit the packet even if there are other packets in flight.- See Also:
IMMEDIATE_RETX_BURST
static final int IMMEDIATE_RETX_BURSTNumber of retransmits sent directly per RTO fire before remaining retransmits are paced, to avoid flooding the I2CP queue with a single-timer-fire burst. @since 0.9.71+.- See Also:
MAX_PACED_RETX
static final int MAX_PACED_RETXOnce a packet has been transmitted this many times, always send it directly instead of pacing it: a repeatedly-resent packet is recovery-critical and must not starve behind a stale pacing rate in the paced queue (hard drain deadline). @since 0.9.71+.- See Also:
PACING_SLOWSTART_THRESHOLD
static final int PACING_SLOWSTART_THRESHOLDWindow size below which pacing is bypassed to avoid the stale-initial-RTT throttle. During slow-start the window itself is the bottleneck and the pacing rate (computed fromDEFAULT_INITIAL_RTO = 2s) would cap throughput at ~108 KB/s regardless of actual path capacity.- See Also:
_resendEventPool
Pool of reusable ResendPacketEvent instances for fast retransmit.
Constructor Details
Connection
public Connection(I2PAppContext ctx, ConnectionManager manager, I2PSession session, SchedulerChooser chooser, SimpleTimer2 timer, PacketQueue queue, ConnectionPacketHandler handler, ConnectionOptions opts, boolean isInbound) Constructor for this connection.- Parameters:
ctx- application context supplying the clock, logger and statsmanager- the owning connection managersession- the I2P session this connection runs overchooser- scheduler used for delayed connectstimer- the shared timer that drives this connection's eventsqueue- the outbound packet queuehandler- the inbound packet handleropts- per-connection options; may be null, a default set is createdisInbound- true for a connection we accepted rather than dialled
Method Details
getMaxResendDelay
public static int getMaxResendDelay()Default resend delay (i2p.streaming.maxResendDelay) in ms.- Returns:
- the router-wide resend delay default in ms
getMinResendDelay
public static int getMinResendDelay()Default resend delay (i2p.streaming.minResendDelay) in ms.- Returns:
- the router-wide resend delay floor in ms
getDisconnectTimeout
public static int getDisconnectTimeout()Idle time after which an established connection is dropped (i2p.streaming.disconnectTimeout) in ms.- Returns:
- the disconnect timeout in ms
getGlobalMaxConnectTimeout
static long getGlobalMaxConnectTimeout()Router-wide absolute cap on the effective connect window, in milliseconds. Defaults to 75 seconds; an operator may raise or lower it withI2PSocketOptions.PROP_MAX_CONNECT_TIMEOUT, and a per-connection override (ConnectionOptions#setMaxConnectTimeout()) wins when set.- Returns:
- the global cap in ms, never negative
getMaxConnectTimeout
long getMaxConnectTimeout()Absolute cap on this connection's connect window, in milliseconds. Uses the per-connection override (ConnectionOptions#getMaxConnectTimeout()) when set, otherwise the router-widei2p.streaming.maxConnectTimeout.- Returns:
- the cap in ms, never negative
setConnectTimeoutMultiplier
static void setConnectTimeoutMultiplier(int pct) Set the connect timeout multiplier, clamped to [30, 200] percent.- Parameters:
pct- the multiplier percentage (100 = no scaling)
getConnectTimeoutMultiplier
static int getConnectTimeoutMultiplier()Current Tuner-scaled connect timeout percentage.- Returns:
- the connect timeout multiplier percentage (100 = no scaling)
computeEffectiveConnectTimeout
static long computeEffectiveConnectTimeout(long desiredTimeoutMs, int multiplier, long maxConnectTimeoutMs) Compute the effective connect timeout (ms) an outbound connect will wait, as used by bothwaitForConnect(int)and the SYN give-up budget ingetMaxSynSends().Scales the desired base window (
connectDelay + connectTimeout, or justconnectTimeout) by the Tuner connect-timeout multiplier, then applies theCONNECT_TIMEOUT_FLOOR_MSfloor and the absolutemaxConnectTimeoutMscap. Both consumers must agree on this value: a SYN retransmit budget shorter than this window was the cause of the spurious "Connection failed" for live-but-slow peers, where the retransmit timer discarded the connection error-free before waitForConnect reached its own timeout.- Parameters:
desiredTimeoutMs- the base window (connectDelay + connectTimeout) in ms; <=0 means no connect timeout is configuredmultiplier- the Tuner connect-timeout multiplier percentage (30-200)maxConnectTimeoutMs- the absolute cap (i2p.streaming.maxConnectTimeout) in ms- Returns:
- the effective window in ms, or 0 when no connect timeout is configured
getGlobalMaxWindowSize
public static int getGlobalMaxWindowSize()Ceiling floor every stream ramps at least to, whatever its own BDP says.- Returns:
- the Tuner-managed global window ceiling, in messages
setGlobalMaxWindowSize
public static void setGlobalMaxWindowSize(int val) Raise or lower the ceiling floor shared by every stream.- Parameters:
val- the global window ceiling in messages, clamped to [128, ABSOLUTE_MAX_WINDOW]
hasOutstandingData
private boolean hasOutstandingData()Whether this connection has unacknowledged data it could be stuck behind.Read without
_outboundPacketsLock: the verdict is evaluated from a timer as well as from the ACK path, and taking the send lock from the timer invites the established ordering problem with the bandwidth estimator. The cheap proxy answers the question the verdict cares about - has anything ever got through, so that silence afterwards is meaningful - without locking.- Returns:
- true once forward progress has been made on this connection
isRetransmitStalledNow
static boolean isRetransmitStalledNow(boolean established, long now, long stallSinceMs, long thresholdMs) Decide whether the retransmit timer has gone long enough without forward ACK progress to call it a stall.Time, not a count of firings. RTO doubles on every firing for an established connection, so "two firings" is about three seconds early in a connection's life and over a minute late in one that has been running - the same number meaning two very different things, and always far too eager at the start. A duration has one meaning.
Requires an established session. Before the handshake completes there is no forward ACK progress to be missing, so a connection still setting up cannot be stalled; counting its ordinary SYN retransmissions produced a large volume of warnings about connections that were never broken.
- Parameters:
established- true once any packet has been acknowledgednow- current clock value in msstallSinceMs- clock value at which the current no-progress run began, or 0thresholdMs- how long without progress counts as a stall- Returns:
- true only for an established connection idle past the threshold
shouldLogStallEdge
static boolean shouldLogStallEdge(boolean stallDetached, boolean alreadyLogged) Whether this firing should produce a stall warning, given the latch state.The stall condition holds for as long as the path is stuck and the timer keeps firing, so an unconditional warning emits once per RTO tick. That produced over 90,000 identical lines in under an hour here, which is worse than no report at all: it buries the lines that carry signal and trains the reader to skip the class. One line per episode, and an INFO when it ends.
- Parameters:
stallDetached- true when this firing meets the stall conditionalreadyLogged- true when this episode has already been reported- Returns:
- true only on the rising edge of a stall
nextRetransmitDelay
static int nextRetransmitDelay(long now, long oldestLastSend, long rto, boolean immediateAlreadyFired) Delay before the next retransmit-timer fire.An overdue head-of-line packet is worth exactly one immediate attempt. Before this, every subsequent ACK re-armed the timer with
schedule(0), so on a stalled connection each arriving ACK enqueued another immediate run. With a large population of stalled connections that is enough to keep a two-thread timer permanently saturated - and because the coalesce task shares that pool, every graph in the router stops recording at once. After the first immediate attempt the timer falls back to a full RTO, which still bounds recovery time without letting the queue grow per ACK.Pure decision, extracted so the once-only rule can be pinned by tests.
- Parameters:
now- current clock value in msoldestLastSend- send time of the oldest unacked packet, or 0 if nonerto- the connection's current RTO in msimmediateAlreadyFired- true if an immediate fire already happened for this overdue episode- Returns:
- delay in ms; 0 requests an immediate fire
computeSynRetransmitInterval
static int computeSynRetransmitInterval(int measuredRttMs, int initialRtoMs) Compute the inter-SYN retransmit interval for an outbound connection, evidence-gated and RTT-aware.SYN retransmission uses a fixed interval (no backoff — there is no congestion to manage before the connection is established), so the total SYN budget is
maxSynResends * interval. The historical behavior is a fixedinitialRtoMs(default 9000) for every connection, which overshoots the 30s connect timeout on a dead path (12 * 9s = 108s) and under-utilizes the window on a healthy path whose round-trip is far below the default.Gate. The measured RTT
measuredRttMsis positive only when there is genuine path evidence for this peer (a valid analytic ACK, or a dampened RTT seeded from the TCB cache for a recently-contacted destination — seeTCBShare). When it is<= 0there is no evidence (fresh or blackholed peer —Received: 0throughout, sogetRTT()never advances past its initial default), and this returns the configuredinitialRtoMsunchanged. A path with no measurement is treated conservatively rather than fast-failed, because shrinking the interval without evidence would be guessing.RTT-aware interval. With evidence, the interval becomes
headroomPct% * measuredRttMs, floored atSYN_RTO_MIN(no storm on a fast fabric) and capped at the configuredinitialRtoMs(never worse than the current default, so a genuinely slow-but-alive path keeps its handshake room). A measured RTT below the 9000ms default therefore packs more SYN attempts into the connect window (better odds of catching a momentarily-congested tunnel) and, if the path is dead, lets SYN resends exhaust near the 30s connect timeout instead of running the full configured budget — so the proxy can emit the "Website Unreachable" page sooner.- Parameters:
measuredRttMs- a recent round-trip time for this peer in ms, or <=0 when no RTT evidence is available (never measured, no cache entry)initialRtoMs- the configured initial RTO (i2p.streaming.initialRTO) in ms- Returns:
- the fixed inter-SYN retransmit interval (ms) to use
computeSynResendBudget
static int computeSynResendBudget(int configuredMax, int synIntervalMs, long connectWindowMs) Compute the effective number of SYN sends before this connection gives up, used bygetMaxSynSends()on each give-up decision.The total SYN budget is
sends * synIntervalMs(fixed interval, no backoff). With RTT evidence the interval can collapse towardSYN_RTO_MIN(750ms), so a fixedconfiguredMaxbudget can end well beforewaitForConnect(int)'s window does —12 * 750ms = 9sagainst a 10000ms floor — leaving waitForConnect to mislabel the handshake as "Connection failed". This returns at least enough sends thatsends * synIntervalMs >= connectWindowMs, so the connect-timeout gate (which reports an accurate error) fires first on genuinely-dead paths, while a live-but-slow peer keeps receiving SYN retransmits for the full window.- Parameters:
configuredMax- the configured static give-up count (maxSynResends)synIntervalMs- the fixed inter-SYN retransmit interval (ms) in useconnectWindowMs- the effective connect window fromcomputeEffectiveConnectTimeout(long, int, long); 0 means no connect timeout is configured- Returns:
- the number of sends before giving up, never less than
configuredMax
computeSynAckResendCap
static int computeSynAckResendCap(long connectWindowMs, long spacingMs) Compute the per-connection SYN-ACK re-send cap: the number of retransmitted SYNs the client can fit within its effective connect window, given the inter-SYN interval in use. Answering past this point is wasted work — by then the client has given up on the handshake — so the cap hard-bounds SYN-ACK minting without ever starving a live-but-slow peer.- Parameters:
connectWindowMs- the client's effective connect window (getEffectiveConnectWindow()); <=0 means no window is configured and no cap is appliedspacingMs- the inter-SYN-AACK spacing in use; <=0 means no spacing is derived and no cap is applied- Returns:
- the cap; never less than 1, or
SYN_ACK_RESEND_MAXwhen no window is configured
shouldResendSynAck
static boolean shouldResendSynAck(long lastSynAckSentOn, int resends, long now, long minSpacingMs, int maxResends) Decide whether to re-send a SYN-ACK for an existing connection in response to a retransmitted SYN.Bound is applied before the SYN-ACK is minted so a throttled retransmit costs nothing (no packet object, no signature, no enqueue). The decision is purely a function of connection send history and wall clock, which keeps the hot path branch-free and the rule testable without a running router.
Rules. A re-send is allowed when:
- fewer than
maxResendsre-sends have already been performed (resends < maxResends), and - either no SYN-ACK has been sent yet in connection history (
lastSynAckSentOn <= 0— nothing to space against), or the interval since the last SYN-ACK is at leastminSpacingMs.
- Parameters:
lastSynAckSentOn- wall-clock ms of the last SYN-ACK sent for the connection (initial or re-send), or <=0 if none yetresends- number of SYN-ACK re-sends already performed for this connectionnow- current wall-clock ms (I2PAppContext.clock())minSpacingMs- minimum interval between re-sends in msmaxResends- maximum SYN-ACK re-sends per connection lifetime- Returns:
- true if a SYN-ACK should be re-sent now
- fewer than
shouldResendSynAck
boolean shouldResendSynAck(long now) Whether this connection should re-send a SYN-ACK for a retransmitted SYN right now, given its own send history. Pure delegate overshouldResendSynAck(long, int, long, long, int)using the RTT-aware spacing this connection actually arms, so the heuristic is exercised through the same code path tests cover.- Parameters:
now- current wall-clock ms fromI2PAppContext.clock()- Returns:
- true if a SYN-ACK should be re-sent now
recordSynAckResend
void recordSynAckResend(long now) Record that a SYN-ACK was sent for this connection atnow(initial re-send fromConnectionHandler#resendSynAckonly. The initial handshake SYN-ACK is a normal outbound packet and is not counted here). This snapshot advances the throttle window so subsequent retransmitted SYNs are spacedSYN_ACK_RESEND_MIN_SPACING_MSafter the last answer.- Parameters:
now- wall-clock ms the SYN-ACK was enqueued
synAckWarnAlreadyLogged
boolean synAckWarnAlreadyLogged()Whether the "re-sending SYN-ACK" WARN has already been logged for this connection, and mark it logged on first check. Prevents a retransmit storm against a single stuck connection from flooding the log with identical WARN lines (observed as ~15% of log volume during the tracker-tunnel storm); the first re-send logs WARN, subsequent ones drop to DEBUG.- Returns:
- true if the WARN was already logged (caller should log at DEBUG), false if this is the first re-send (caller should log at WARN)
getMaxRetransmissionsStatic
public static int getMaxRetransmissionsStatic()Maximum packets to retransmit per timer fire.- Returns:
- the resend batch size cap
setMaxRetransmissions
public static void setMaxRetransmissions(int val) Resend batch size cap, clamped to [8, 128] packets.- Parameters:
val- the resend batch size cap in packets
getMaxSynResendsStatic
public static int getMaxSynResendsStatic()Configured give-up count for an unacknowledged SYN.- Returns:
- the SYN give-up count before
getMaxSynSends()scaling
setMaxSynResends
public static void setMaxSynResends(int val) Configured SYN give-up count, clamped to [3, 16].- Parameters:
val- the SYN give-up count
setNextSendFreshConnection
void setNextSendFreshConnection()Set the flag so the next send through PacketQueue uses a fresh connection, rotating to a different outbound tunnel when the current one has stalled. Cleared on first use.isNextSendFreshConnection
boolean isNextSendFreshConnection()Returns true if the next send should use a fresh connection (tunnel rotation). Cleared on read so only the first send in a soft-failure pass triggers rotation.- Returns:
- true if the next send should rotate to a fresh connection
clearNextSendFreshConnection
void clearNextSendFreshConnection()Clears the pending tunnel-rotation flag, so the next soft-failure pass does not rotate again.getRetransmitCount
int getRetransmitCount()Returns the current retransmit count (consecutive retransmit timer firings without ACK progress). Used by external callers to detect stalled downloads.- Returns:
- the consecutive no-progress retransmit firings
isRetransmitStall
boolean isRetransmitStall()Returns true if the retransmit count exceeds the stall threshold, indicating a stalled download on a slow/lossy path.- Returns:
- true when two or more firings have passed with no ACK progress
getTimeSinceLastSend
long getTimeSinceLastSend()Returns the number of milliseconds between the last send and now, for external stall detection callers.- Returns:
- milliseconds elapsed since the last send
getMaxRtx
private int getMaxRtx()Maximum number of packets to retransmit in a single timer fire. This caps the resend batch size only; the give-up threshold isConnectionOptions.getMaxResends(), checked separately. Tunable viai2p.streaming.maxRetransmissions(default: 32).- Returns:
- the max rtx
getSynRetransmitInterval
private int getSynRetransmitInterval()The fixed interval between SYN retransmits for this connection, evidence-gated.Delegate for
computeSynRetransmitInterval(int, int)using this connection's current path evidence (ConnectionOptions#getRTT()is a real measurement only onceConnectionOptions#receivedAck()is true — either a valid handshake ACK or a dampened TCB-cache value seeded from a prior connection to this destination) and the configured initial RTO. Pure so the give-up math is testable directly throughcomputeSynRetransmitInterval(int, int).- Returns:
- the fixed inter-SYN retransmit interval (ms) to arm the retransmit timer with
getMaxSynSends
private int getMaxSynSends()Effective SYN give-up count for this connection:maxSynResendsscaled up so the retransmit budget (count * inter-SYN interval) covers the connect window, so the retransmit timer cannot tear this connection down whilewaitForConnect(int)is still waiting.The base window mirrors
ConnectionManager.connect(Destination, ConnectionOptions, I2PSession): connectDelay is added to connectTimeout only on the delayed-SYN path, which is exactly how waitForConnect is invoked there (connectDelay + connectTimeout).- Returns:
- the number of SYN sends before giving up on an unacknowledged SYN
computeConnectBase
static long computeConnectBase(long connectTimeoutMs, long connectDelayMs) Assemble the un-scaled base connect window (ms) for a connection's options.connectDelay is added to connectTimeout only on the delayed-SYN path (both set), so the base matches how
ConnectionManager.connect(Destination, ConnectionOptions, I2PSession)invokeswaitForConnect(int)— there the SYN has not yet been sent andconnectDelayis added to the timeout so the handshake is not cut short while the SYN is still waiting to be transmitted.- Parameters:
connectTimeoutMs- the option's connect timeout in ms (<=0 means none)connectDelayMs- the option's connect delay in ms- Returns:
- the base connect window in ms; 0 when no connect timeout is configured
getEffectiveConnectWindow
long getEffectiveConnectWindow()The total time (ms) from connection creation during which this connecting connection may wait for its SYN to be acknowledged before the connect path tears the handshake down.This is the single authoritative connect window shared by
waitForConnect(int),getMaxSynSends(), andSchedulerConnecting, so no path can tear an unacknowledged SYN down before the others have given it the full Tuner-scaled budget. It is the (connectDelay + connectTimeout) base — connectDelay counted only on the delayed-SYN path, exactly asConnectionManager.connect(Destination, ConnectionOptions, I2PSession)invokes waitForConnect — scaled by the Tuner's connect-timeout multiplier, floored atCONNECT_TIMEOUT_FLOOR_MS, and capped at the absolute max connect timeout.Using the raw un-scaled connectTimeout here (as SchedulerConnecting once did) lets the scheduler race ahead of waitForConnect on high-RTT paths where the Tuner has raised the multiplier above 100%, killing the handshake with a generic error instead of the accurate "SYN not acknowledged".
- Returns:
- the effective connect window in milliseconds; 0 when no connect timeout is configured (no budget, caller should not give up on timeout)
calculatePacingRate
private long calculatePacingRate()Calculate pacing rate based on current congestion window and RTT. Rate = (cwnd * mss) / rtt to smooth transmission.- Returns:
- the pacing rate in bytes per second, never below
ConnectionOptions.getMinPacingRate(), orLong.MAX_VALUEwhen the RTT, window or MSS is still unset (pacing disabled)
updatePacingRate
private void updatePacingRate()Refresh the cached pacing rate when the congestion window or RTT changes.calculatePacingDelay(int)reads this cache rather than recomputing the rate per packet, so it must be called whenever an input tocalculatePacingRate()changes.calculatePacingDelay
private long calculatePacingDelay(int packetSize) Calculate delay needed for pacing based on packet size and current rate. Pacing delay = (packetSize / rate) - timeSinceLastPacket.Uses the cached
_pacingRate(refreshed on every window / RTT change) instead of recomputing cwnd·mss/rtt per packet.- Parameters:
packetSize- size in bytes of the packet about to go out- Returns:
- delay in ms, 0 if no pacing needed
calculatePacingDelay
private long calculatePacingDelay(int packetSize, long pacingRate) Same as above, but for a rate the caller has already computed.- Parameters:
packetSize- size in bytes of the packet about to go outpacingRate- rate in bytes per second; must be a real rate, as theLong.MAX_VALUE"no pacing" sentinel is screened by the caller- Returns:
- delay in ms, 0 if no pacing needed
getSSThresh
int getSSThresh()Current slow-start threshold: the window at which congestion avoidance takes over from exponential slow start.- Returns:
- the slow-start threshold in messages
initialSsthresh
static int initialSsthresh(int maxSlowStartWindow, int globalMaxWindow, int initialWindowSize) The slow-start threshold a freshly constructed connection starts with, clamped so it can never exceed the connection's window ceiling.ConnectionPacketHandler.adjustWindow()picks its growth path onwindow < ssthresh: below is exponential slow start, at or above is linear congestion avoidance. The ceiling fromcomputeWindowCeiling(int, float, int, int, int)ismin(absMax, max(globalMax, initialWindowSize))before any BDP term, so a connection can never grow past that base. Initialisingssthreshto the Tuner-managedmaxSlowStartWindowtherefore risksssthresh > ceilingwhenever the Tuner has raised the slow-start cap above the window ceiling — and thenwindow < ssthreshis permanently true, the deficit-multiplier recovery path in congestion avoidance never runs, and every window change is governed by the slow-start branch alone.Both inputs are Tuner-tunable over overlapping ranges (
maxWindowSize512-8192,maxSlowStartWindow128-8192), so nothing else maintains thessthresh <= ceilinginvariant; this is where it is enforced. Clamping to the pre-BDP base rather thangetWindowCeiling()keeps the constructor free of the bandwidth estimator, which is not yet constructed at this point.- Parameters:
maxSlowStartWindow- the Tuner-managed slow-start cap, in messagesglobalMaxWindow- the Tuner-managed window ceiling floor (i2p.streaming.maxWindowSize), in messagesinitialWindowSize- the configured starting window, in messages- Returns:
- the initial slow-start threshold in messages, in
[1, maxSlowStartWindow]
getNextOutboundPacketNum
public long getNextOutboundPacketNum()Next outbound packet sequence number.- Returns:
- the next sequence number
packetSendChoke
This doesn't "send a choke". Rather, it blocks if the outbound window is full, thus choking the sender that calls this.Block until there is an open outbound packet slot or the write timeout expires. PacketLocal is the only caller, generally with -1.
A persist timer prevents deadlock when the remote chokes us (window=1) and then never sends an unchoke signal. After RTO (min 5s), we auto-unchoke and allow the retransmission mechanism to restore the window, with exponential backoff so a persistently choked peer does not cause a tight probe loop.
The wait loop polls every 50ms (instead of 250ms) to reduce latency when an ACK arrives to free a window slot.
- Parameters:
timeoutMs- 0 or negative means wait forever, 5 minutes max- Returns:
- true if the packet should be sent, false for a fatal error will return false after 5 minutes even if timeoutMs is <= 0.
- Throws:
IOException- if the peer reset the connection, or either side closed the socket, while waiting for a window slotInterruptedException- if interrupted while waiting on the window lock
isConnectedOrError
Assert the connection is still usable, throwing instead of reporting it.- Returns:
- true, always; unusable connections throw rather than return false
- Throws:
IOException- if the peer reset, the socket closed, or the output stream closed
getWindowCeiling
int getWindowCeiling()Per-stream window ceiling: the minimum ofABSOLUTE_MAX_WINDOWand the larger of the Tuner-managed global ceiling (or the per-connection override) and this stream's own bandwidth-delay product scaled byWINDOW_CEILING_HEADROOM_PCT. The global value is the FLOOR of the cap. Every stream can always ramp to the Tuner's base capacity — while a proven pipe lifts its own ceiling toward the absolute cap. Each stream responds to its own RTT and delivered-bandwidth estimate, and the Tuner still pulls the floor down under memory or loss pressure whileABSOLUTE_MAX_WINDOWbounds every stream unconditionally.Before any ACK sample exists the ceiling falls back to the global floor, so an uncalibrated stream ramps exactly as before (bounded by slow-start's ssthresh and the global ceiling).
The result is cached for
BDP_CACHE_MSso a persistently full window (the app keeps writing, the choke loop keeps blocking) does not re-lock the synchronized estimator and re-read RTT on every write attempt; the Westwood+ sample only moves on ACK cadence anyway. Both writer threads may populate the cache; a ceiling from either is valid, and ceiling + timestamp are stored as one value (WindowCeilingSample) so a reader can never pair one writer's ceiling with the other writer's newer timestamp — which would pin the cache hit to the older ceiling for extra BDP_CACHE_MS windows — nor invert the age check by pairing a stale timestamp with a fresh ceiling.- Returns:
- max allowed in-flight packets for this stream, at least the global/floor base and never above
ABSOLUTE_MAX_WINDOW
computeWindowCeiling
static int computeWindowCeiling(int globalMax, float bwePerMs, int rttMs, int floorMsgs, int absMaxMsgs) Pure decision helper forgetWindowCeiling(): the per-stream window ceiling in messages —min(absMax, max(globalMax, floor, headroom * bwe * rtt)), falling back tomin(absMax, max(globalMax, floor))when no bandwidth sample exists yet.The Tuner global is the FLOOR of the cap, never its ceiling: a measured-goodput BDP term that binds downward is self-defeating — the window gets pinned at the floor, measured goodput cannot grow past the pinned window, and the ceiling stays pinned there forever (the observed 128-message absorbing state that capped a fast path at ~85 KB/s). With the global as the floor, slow start and congestion avoidance always ramp to at least the Tuner base, the BDP term may lift the cap toward
absMaxMsgsonce the path proves capacity, loss response stays with the graduated window cuts, and the Tuner's memory/loss shrink still lowers the guaranteed base on every stream.- Parameters:
globalMax- Tuner-managed global or per-connection ceiling in messages; the guaranteed floorbwePerMs- Westwood+ estimate in packets/ms; NaN or<= 0means no sample yetrttMs- round-trip time in ms (caller applies the 500ms floor)floorMsgs- minimum ceiling regardless of the estimate (the initial window, so the cap is never below a connection's starting cwnd)absMaxMsgs- absolute ceiling regardless of inputs (ABSOLUTE_MAX_WINDOW)- Returns:
- ceiling in messages, in
[min(absMax, max(1, globalMax, floorMsgs)), absMaxMsgs]
getPTO
private int getPTO()Tail Loss Probe timeout (PTO). Time after which we send a probe if no ACK has been received for the oldest unacked packet. Fires at ~2*RTT to detect loss before RTO.- Returns:
- probe timeout in ms — [200, 2000] once the RTT is known, otherwise bounded by half the current RTO (up to 3000)
shouldWait
private boolean shouldWait(int unacked, int wsz, int windowCeiling) Whether the sender should block.- Parameters:
unacked- number of unacked packetswsz- the current window sizewindowCeiling- per-stream window ceiling fromgetWindowCeiling(), sampled outside the lock- Returns:
- true if the sender should block (window full or choked)
shouldLogPersistWarn
static boolean shouldLogPersistWarn(long lastWarnTime, long now, long minIntervalMs) Whether to log another persist-timer auto-unchoke WARN. Guard: emit at most one WARN per persist interval per connection. The persist branch inpacketSendChoke(long)is re-armed on every block and its backoff is a per-call local, so an application that keeps writing into a persistently choked window would otherwise log on every write attempt.- Parameters:
lastWarnTime- last timestamp the WARN was emitted, 0 if nevernow- router clock time in millisecondsminIntervalMs- minimum ms between WARNs (the persist delay itself)- Returns:
- true if the WARN should be emitted
shouldSampleChokeSizeEnd
static boolean shouldSampleChokeSizeEnd(int counter) Whether a completed packet release should emit astream.chokeSizeEndsample.Pure helper for the send branch of
packetSendChoke(long)so the sampling cadence is unit-testable without a router. The caller increments its counter once per successful send and emits when this returns true.- Parameters:
counter- post-increment value of the per-connection end-sample counter- Returns:
- true if this release should be recorded
shouldSampleChokeSizeBegin
static boolean shouldSampleChokeSizeBegin(int counter) Whether a first-block iteration should emit astream.chokeSizeBeginsample.Pure helper for the wait branch of
packetSendChoke(long). The stat exists to tell the Tuner how deep the outbound window was when we started blocking, so it must be emitted whilestartedis still false — that is, on the iterations that actually observe the window full.The prior expression combined
!startedwith a power-of-two mask on a counter incremented in the same statement, so the only iterations that could satisfy both were the 1st (counter=1) and the 17th — and the 17th hasstarted == trueand is filtered out. No iteration could ever emit, leaving the stat permanently zero and every Tuner parameter readingstream.chokeSizeBeginpinned at its default.- Parameters:
counter- post-increment value of the per-connection begin-sample counter- Returns:
- true if this first block should be recorded
stallVerdict
static Connection.StallState stallVerdict(long now, long lastProgressAt, double observedBps, double ratio, int samples, double baselineBps, double absFloorBps, long stallGraceMs) Decide whether a connection is delivering far below its own established rate, is fully stalled, or is healthy.Pure, so the arming rules are testable without a router. The order matters: a hard stall is reported even when the baseline is unknown, because "no bytes at all" is unambiguous on its own, while a throttle verdict is only available once there is a baseline to compare against.
A throttle requires
observedBps > 0. A window that saw no bytes is either a stall — which the grace above already covers — or simply a quiet window, and a latency-bound stream produces those routinely, so scoring them as throttled would report a fault on a healthy path.- Parameters:
now- current clock valuelastProgressAt- clock value of the last observed progressobservedBps- goodput measured over the current sample windowratio- fraction of baseline below which the link is throttledsamples- completed goodput samples backing the baselinebaselineBps- EWMA baseline, 0 if not yet establishedabsFloorBps- absolute floor capping the proportional floorstallGraceMs- time with no bytes before a hard stall is reported- Returns:
- the verdict; never null
stallVerdict
static Connection.StallState stallVerdict(long now, long lastProgressAt, double observedBps, double ratio, int samples, double baselineBps, double absFloorBps, long stallGraceMs, boolean outstandingData) Decide the stall verdict for one window, told whether anything is actually in flight to be stalled on.outstandingDatais the guard against the false positive that made this detector harmful: a download that is simply waiting on a slow peer sends nothing, so nothing is acknowledged, so an ACK-only rule concludes the path is dead and marks the destination for tunnel rotation. The transfer was never broken; the churn it caused was. With nothing in flight there is nothing to be stuck behind.- Parameters:
now- clock value at the sample boundarylastProgressAt- clock value of the last observed progress, or 0 for noneobservedBps- goodput measured over the current sample windowratio- fraction of baseline below which the link is throttledsamples- completed goodput samples backing the baselinebaselineBps- EWMA baseline, 0 if not yet establishedabsFloorBps- absolute floor capping the proportional floorstallGraceMs- time with no bytes before a hard stall is reportedoutstandingData- true when unacknowledged data is waiting on the wire- Returns:
- the verdict; never null
updatedBaseline
static double updatedBaseline(double baseline, double sampleBps, int percent) Fold a completed goodput sample into the baseline EWMA.Pure. The baseline tracks capacity changes rather than freezing at the first observation, but it moves slowly enough that a transient dip does not immediately become the new reference the next window is judged against — without that, a single slow window would excuse itself.
- Parameters:
baseline- current EWMA, 0 if nonesampleBps- goodput of the sample just completedpercent- weight of the new sample, scaled by 100- Returns:
- the updated EWMA
sampleRateBps
static double sampleRateBps(long bytes, long elapsedMs) Roll the goodput sample forward, returning the rate of the window that just closed.Pure. A window that closed having seen no bytes reports 0, which the caller feeds to
updatedBaseline(double, double, int)— a quiet window must not be allowed to pull the baseline to zero and disarm the detector.- Parameters:
bytes- bytes counted in the closing windowelapsedMs- duration of the closing window- Returns:
- goodput in bytes/sec, 0 if the window was too short to measure
noteProgress
private void noteProgress(int ackedPackets, int messageSize) Record acknowledged progress and, when a goodput window has closed, fold its rate into the baseline and evaluate the floor.Called from the ACK path outside
_outboundPacketsLock. Only the rising edge of a verdict is reported, so a persistently throttled connection does not re-report on every ACK.- Parameters:
ackedPackets- packets acknowledged in this batchmessageSize- negotiated message size, used to convert to bytes
evaluateFloor
private void evaluateFloor(long now, double bps) Roll the baseline forward and report the verdict for one closed window.The baseline is only moved by a window that met the floor, or by a long enough run of windows that did not. Letting a single slow window pull the baseline down is what makes a self-referential floor disarm itself: the reference falls to meet the observation and the next window trivially passes.
- Parameters:
now- clock value the window closed atbps- goodput measured over the window
reportStallState
Emitstream.con.stallDetectedandstream.con.throttleDetected. on the rising edge of a verdict.- Parameters:
verdict- the StallState just computed, compared against the last one
markStalledForRotation
private void markStalledForRotation()Publish a stall to the router so outbound tunnel selection can prefer a different path for this destination.Only STALLED marks, not THROTTLED: a slow-but-delivering path is not proven dead, and rotating on it would churn a destination that simply has no better route. A hard stall is unambiguous.
The connection is deliberately left open. Egress is chosen per message in the router, so the router can move this stream onto another tunnel without touching it, and the client sees a retransmitted gap rather than an aborted download.
retransmitCount
static int retransmitCount(int inFlight, int maxRtx) How many packets a retransmission timeout resends: at most half the packets in flight (RFC 5681 section 4.3), never more than the retransmit cap, and at least one so a lone lost packet is not skipped.- Parameters:
inFlight- unacked packets in the window, >= 1maxRtx- the retransmit cap, >= 1- Returns:
- how many of the oldest in-flight packets to resend
graduatedLossWindow
static int graduatedLossWindow(int strikes, int wsize) Graduated congestion response: the window is cut by an amount that scales with the number of CONSECUTIVE loss events since the last recovery, so a single failed packet does not collapse the connection while persistent loss still backs off.The 0.9.71+ profile ("ramp fast, decline slowly") makes every tier gentler than the classic halves/quarters — a first strike cuts only 1/8, a second another 1/4, and persistent loss settles at half — so a single loss event on a high-BDP connection does not undo an RTT of window growth, while genuinely congested paths still concede capacity.
- Parameters:
strikes- consecutive loss events (>= 1) since the last recoverywsize- current window size- Returns:
- new window size, floored at 4 (never below a usable minimum)
graduatedLossSsthresh
static int graduatedLossSsthresh(int strikes, int wsize, int bwBasedSsthresh) Slow-start threshold for a graduated loss response: never below the graduated window (so the connection can regrow to at least its post-cut capacity) and never below the bandwidth-derived estimate.- Parameters:
strikes- consecutive loss events since the last recoverywsize- current window sizebwBasedSsthresh- the bandwidth-estimate-derived threshold (>= 1)- Returns:
- slow-start threshold, floored at 1
bandwidthSsthreshAnchor
static int bandwidthSsthreshAnchor(float bwEstPacketsPerMs, int minRttMs, int factor, int floor) The bandwidth-derived slow-start threshold anchor: the packets the pipe can hold, estimated as measured bandwidth times the effective RTT times the growth factor, floored at the given minimum.The effective RTT is floored at
SS_THRESH_BW_ANCHOR_MIN_RTT(1s): a raw sub-second min-RTT would otherwise shrink the anchor below the classic fast-retransmit floor (16 packets) on a healthy path, and once the window is pinned there the low ACK rate lowers the measured bandwidth, confirming the low anchor — a self-reinforcing ~10KB/s lock.- Parameters:
bwEstPacketsPerMs- measured bandwidth in packets/ms (>= 0)minRttMs- the connection's raw min-RTT in ms (>= 0)factor- growth factor applied to the BDP estimate (>= 1)floor- minimum anchor in packets (>= 1)- Returns:
- anchor in packets, never below
floor
lossEpisodeRecovered
static boolean lossEpisodeRecovered(boolean hadCongestionMark, long ackThrough, long lastCongestionHighestUnacked, int activeResends) Whether a congestion episode's lost window has been fully recovered.Recovery is declared once the ACK stream has passed the congestion mark and no retransmits remain outstanding. The window itself need not be empty: a pipe dribbling data must not stay locked in the graduated tiers merely because it never fully drains. Requiring zero outstanding retransmits still guards against a stale ACK claiming recovery on a NACK path before the paced resends have cleared.
- Parameters:
hadCongestionMark- true when a congestion cut has ever occurredackThrough- highest contiguously-acked sequence numberlastCongestionHighestUnacked- highest unacked sequence number at the cut, -1 if noneactiveResends- retransmits still awaiting their ACK- Returns:
- true when the lost window has been recovered
decayLossStrikes
static int decayLossStrikes(int strikes, long lastLossTime, long now, long smoothedRtt) Strike decay: a single loss strike is forgiven once at least one smoothed RTT (floored at 500 ms) has passed without another strike, rolling the graduated backoff back down toward the gentle tier. One-off losses are normal on I2P, and keying the decay to elapsed time rather than ACK count means a stale lone strike cannot keep the window depressed forever, while true back-to-back loss (strikes inside the RTT window) still escalates.Pure and side-effect free for unit testing; call sites apply the result and refresh
_lastLossStrikeTimeunder_outboundPacketsLock.- Parameters:
strikes- current consecutive-loss count (>= 0)lastLossTime- wall-clock ms of the most recent strike (<= 0 if none)now- wall-clock ms to compare the elapsed interval againstsmoothedRtt- the connection's smoothed RTT in ms (>= 0)- Returns:
- strikes - 1 when at least max(smoothedRtt, 500) ms have elapsed since the last strike, otherwise the current count; never below 0
stuckLifetimeExceeded
static boolean stuckLifetimeExceeded(long budgetMs, long now, long createdOn) Pure predicate: has the oldest unacked packet been in flight beyond the worst-case retransmit budget, measured from CREATION?The worst case is one transmission per RTO for the full per-packet retransmit budget, which the caller computes from the configured
maxResends * maxRtobefore calling. Anchoring the budget at the packet's creation (instead of its last transmission) gives a fixed wall-clock deadline that retransmission activity cannot extend: an established connection in resume mode keeps retransmitting a budget-exhausted head-of-line packet every RTO (refreshing its last send time) precisely so it can surge forward when the path clears, and that deadline must still hold so a genuinely dead path is closed rather than retried forever. An exact-inequality comparison keeps a live path on the budget boundary from being killed.- Parameters:
budgetMs- the worst-case in-flight budget in ms, computed by the caller as the retransmit budget times the maximum single retransmit timeout;<= 0disables the checknow- current time in ms since epochcreatedOn- creation time of the oldest packet, in ms since epoch (PacketLocal.getCreatedOn()), or <= 0 if unknown- Returns:
- true if the packet has been in flight beyond the budget and recovery is presumed dead
remoteSilentTooLong
static boolean remoteSilentTooLong(long lastReceivedOn, int inactivityTimeout, long now) Pure predicate: has the remote sent nothing at all for a full inactivity window, while unacked packets are still in flight?An ACK (or a data packet) is emitted by a live remote on receipt within the ack window, so a connection that has unacked in-flight packets AND has received zero bytes for the configured inactivity timeout can no longer be making progress in either direction: either our sends are not arriving (dead outbound leg) or the remote is gone. Holding such a connection longer cannot resume it — per the protocol the remote's own inactivity timer (default 120s,
i2p.streaming.inactivityTimeout) has already closed it, and its close is lost on the dead path back. This gives an established connection in resume mode — which otherwise retransmits a budget-exhausted head-of-line packet every RTO until the creation-anchored backstop (stuckLifetimeExceeded(long, long, long), defaulti2p.streaming.stallGiveupMs= 90s) fires — a tighter wall-clock deadline so the application gets EOF within ~one inactivity window instead of hanging on a zombie stream it can never resume.Anchoring at the last RECEIVED packet (not the last send) is deliberate: resume mode refreshes the last send time on every retransmit, so a last-send-anchored check would defer forever. A receive-anchored check only fires when the remote has truly gone silent; a healthy path that acknowledges our data — even slowly — resets
lastReceivedOnand never hits this bound. An exact-inequality comparison keeps a connection on the boundary alive.- Parameters:
lastReceivedOn- timestamp of the last packet received from the remote, in ms since epoch (Connection#packetReceived()), or <= 0 if nothing has ever been received (connect phase, which is bounded separately by the SYN give-up budget)inactivityTimeout- configured inactivity timeout in ms (> 0)now- current time in ms since epoch- Returns:
- true if the remote has been silent for the full inactivity window while unacked packets are in flight
effectiveInactivityTimeout
static int effectiveInactivityTimeout(int configured, int fallbackMs) Effective inactivity window for the remote-silence bound.The predicate requires a positive window and is silently disarmed by a
<= 0configured timeout, which would leave a dead path hanging until the creation-anchored backstop (stuckLifetimeExceeded(long, long, long)) — by default 90s resends * maxRTO ≈ 6 min. Floor the window at the protocol default (120s) so a zombie stream still dies within ~one inactivity window; the bound is deliberately never tightened below that.- Parameters:
configured- the connection's configured inactivity timeout in msfallbackMs- the floor to use when the configured window is not positive- Returns:
configuredifconfigured > 0, elsefallbackMs
hardResendBudgetExceeded
static boolean hardResendBudgetExceeded(int totalSends, int softResends, int maxResends) Pure decision: has the number of HARD send attempts (sends that took the router's reply as something to retry after, i.e. not router soft failures) exceeded the retransmit budget?A router soft failure (NO_TUNNELS / EXPIRED / LOCAL) means the message was never put on the tunnel fabric, so the attempt is not evidence of on-wire loss. Counting soft-failure-triggered resends against
maxResendsmakes a long stream abort during a tunnel handover: each soft failure immediately re-fires the retransmit timer, so ~30 router-side failures exhaust the budget and kill the connection even though the packet never left. Only genuine loss should consume the budget. The subtraction is clamped at zero so an early-tagged paced resend cannot undercount below the true hard count.- Parameters:
totalSends- total send attempts incl. resends (PacketLocal.getNumSends())softResends- of those, resends tagged as soft-failure-triggered (PacketLocal.getNumSoftResends())maxResends- configured per-packet retransmit budget (> 0)- Returns:
- true if the non-soft send attempts exceed the budget
budgetExhaustionClosesConnection
static boolean budgetExhaustionClosesConnection(boolean budgetExhausted, boolean hasForwardProgress) Pure decision: should an exhausted per-packet retransmit budget close the connection?Once a connection has made forward progress (any packet acknowledged) it may carry an in-flight download with app-level state to preserve. A single head-of-line packet that exhausts its budget is then evidence of a TEMPORARY path failure (a hole at a congested hop), not a dead path: tearing the whole stream down forces the application to reconnect and re-transfer, which is the observed "download fails and restarts" churn. Such connections keep the packet in the window and keep retransmitting — they resume — instead of closing. Only a connection that never got a single packet through (connect phase, nothing to resume) keeps the fatal close.
The
budgetExhaustedinput already excludes SYN and CLOSE packets: connect-phase give-up is decided separately (synGiveUpBudgetExceeded(int, int, int)with a distinct error), and CLOSE packets are bounded by the dedicated close-resend cap.- Parameters:
budgetExhausted- true if a non-SYN, non-CLOSE packet's hard send count has exceeded its retransmit budgethasForwardProgress- true if any packet has been acknowledged on the connection (Connection._highestAckedThrough >= 0)- Returns:
- true if the connection should close (budget exhausted AND no forward progress); false if it should resume over closing
synGiveUpBudgetExceeded
static boolean synGiveUpBudgetExceeded(int totalSends, int softResends, int maxSynSends) Pure decision: has the retransmit loop exceeded the SYN give-up budget after excluding soft-failure-triggered resends?Mirror of
hardResendBudgetExceeded(int, int, int)for the SYN-phase give-up. A SYN resent only because the router reported a soft failure (e.g. NO_LEASESET while its LeaseSet fetch is in flight) never reached the tunnel fabric, so it is not evidence of a dead path; counting it againstmaxSynSendslets a burst of router-side soft failures kill a connect in a few hundred ms with a spurious "SYN not acknowledged". Only genuine on-wire retransmits should consume the give-up budget.- Parameters:
totalSends- total SYN send attempts incl. resends (PacketLocal.getNumSends())softResends- of those, resends tagged as soft-failure-triggered (PacketLocal.getNumSoftResends())maxSynSends- per-connection SYN give-up budget (getMaxSynSends())- Returns:
- true if the non-soft SYN send attempts exceed the budget
shouldPaceRetx
static boolean shouldPaceRetx(int burstCount, int nResends) Pure decision: should this packet's retransmit be paced through the paced queue rather than sent directly?The first
IMMEDIATE_RETX_BURSTpackets of a timer fire are sent directly to avoid flooding the I2CP queue with a single-timer-fire burst. Beyond that, pacing spreads the load. Packets already transmittedMAX_PACED_RETXtimes or more are recovery-critical and always sent directly so they cannot starve behind a stale pacing rate in the paced queue (hard drain deadline).- Parameters:
burstCount- number of direct sends already made in this timer firenResends- transmissions this packet has undergone- Returns:
- true if the retransmit should go through pacing
windowAdjusted
void windowAdjusted()Notify all threads waiting inpacketSendChoke(long). Also update pacing rate since window size may have changed.ackImmediately
void ackImmediately()Ack immediately.sendReset
private void sendReset()Got a packet we shouldn't have, send 'em a reset. More than one reset may be sent.sendAvailable
void sendAvailable()Flush any data that we can. Non-blocking.scheduleSoftFailureRetransmit
void scheduleSoftFailureRetransmit()Trigger immediate retransmission of unacked packets after a soft failure (e.g., tunnel expiry, no tunnels). This schedules the retransmit event with zero delay to retry sending on a new tunnel without waiting for RTO.Immediate reschedules are rate-limited: when the router soft-fails every message (STATUS_SEND_FAILURE_NO_LEASESET while the LeaseSet fetch for a fresh destination is still in flight), each report used to cancel the retransmit timer and fire IMMEDIATELY. Back-to-back reports arrived at the I2CP round-trip rate (~10ms) and each immediate pass bumped the SYN's send count once, exhausting the SYN give-up budget (getMaxSynSends()) in ~200ms and killing the connect with a spurious "SYN not acknowledged" before the LeaseSet could be fetched. During the SYN phase the minimum gap is the SYN retransmit interval, so retries are interval-spaced exactly as the give-up budget math assumes; for established connections a small fixed floor prevents any spin while still hopping a replacement tunnel far sooner than the (possibly doubled) RTO.
shouldRateLimitSoftRetransmit
static boolean shouldRateLimitSoftRetransmit(long now, long lastSoftFailRetransmit, long minSpacingMs) Pure decision: is this soft-failure report too close to the last permitted immediate retransmit to fire another now?Derived from
scheduleSoftFailureRetransmit(); extracted for testability. A report at exactlyminSpacingMsafter the last is allowed, mirroring the strict-inequality pacing of the retransmit timer.- Parameters:
now- wall-clock time of this soft-failure report (ms)lastSoftFailRetransmit- wall-clock time of the last permitted immediate retransmit (ms), 0 if none yet this connectionminSpacingMs- minimum gap between immediate retransmits (ms)- Returns:
- true if the retransmit should be deferred to the existing timer
sendPacket
This sends all 'normal' packets (acks and data) for the first time. Retransmits are done in ResendPacketEvent below. Resets, pings, and pongs are done elsewhere in this class, or inConnectionManageror ConnectionHandler.- Parameters:
packet- the ACK or data packet to send for the first time; null is ignored
ackPackets
Process the acks and nacks received in a packet.Note: the returned list is a reused, internally-owned buffer (
_ackedList) that is cleared at the start of the nextackPackets()call. Callers MUST fully consume it before the next ack is processed. This is safe because acks are serialized on the receive thread; do not retain a reference across calls.- Parameters:
ackThrough- the highest packet sequence number the peer has received contiguously; anything lower counts as ACKed unless NACKednacks- sequence numbers explicitly reported as lost, or null or empty if the peer sent none- Returns:
- List of packets acked for the first time (empty if none); a shared mutable buffer, not a fresh copy
eventOccurred
void eventOccurred()Notify the scheduler that an event occurred on this connection.notifyCloseSent
public void notifyCloseSent()Called by CPH when a CLOSE packet is sent. Idempotent: only the first call sets the timestamp.shouldLogDuplicateClose
private static boolean shouldLogDuplicateClose(long now) Rate-limit the duplicate-close report.- Parameters:
now- current time in milliseconds- Returns:
- true if this occurrence should be reported
closeReceived
public void closeReceived()Notify that a close was received. Called by CPH. May be called multiple times.notifyLastPacketAcked
public void notifyLastPacketAcked()Notify that a close that we sent, and all previous packets, were acked. Called by CPH. Only call this once.resetReceived
public void resetReceived()Notify that a reset was received. May be called multiple times.getResetReceived
public boolean getResetReceived()Check if a reset has been received on this connection.- Returns:
- true if a reset was received
getResetReceivedOn
public long getResetReceivedOn()Timestamp when a reset was received.- Returns:
- 0 if not received
isInbound
public boolean isInbound()Check if this is an inbound connection.- Returns:
- true if inbound
getIsConnected
public boolean getIsConnected()Always true at the start, even if we haven't gotten a reply on an outbound connection. Only set to false on disconnect. For outbound, usegetHighestAckedThrough()>= 0 also, to determine if the connection is up.In general, this is true until either: - CLOSE received and CLOSE sent and our CLOSE is acked - RESET received or sent - closed on the socket side.
- Returns:
- the is connected
getHardDisconnected
public boolean getHardDisconnected()Check if this connection has been hard-disconnected (via RESET).- Returns:
- true if hard-disconnected
getResetSent
public boolean getResetSent()Check if a reset has been sent on this connection.- Returns:
- true if a reset was sent
getResetSentOn
public long getResetSentOn()Timestamp when a reset was sent.- Returns:
- 0 if not sent
getDisconnectScheduledOn
public long getDisconnectScheduledOn()Timestamp when the disconnect was scheduled.- Returns:
- 0 if not scheduled
disconnect
public void disconnect(boolean cleanDisconnect) Must be called when we are done with this connection. Enters TIME-WAIT if necessary, and removes from connection manager. May be called multiple times. This closes the socket side. In normal operation, this is called when a CLOSE has been received, AND a CLOSE has been sent, AND EITHER: received close before sent close AND our CLOSE has been acked OR received close after sent close.- Parameters:
cleanDisconnect- if true, normal close; if false, send a RESET
disconnect
public void disconnect(boolean cleanDisconnect, boolean removeFromConMgr) Must be called when we are done with this connection. May be called multiple times. This closes the socket side. In normal operation, this is called when a CLOSE has been received, AND a CLOSE has been sent, AND EITHER: received close before sent close AND our CLOSE has been acked OR received close after sent close.- Parameters:
cleanDisconnect- if true, normal close; if false, send a RESETremoveFromConMgr- if true, enters TIME-WAIT if necessary. if false, MUST calldisconnectComplete()later. Should always be true unless called from ConnectionManager.
disconnectComplete
public void disconnectComplete()Must be called when we are done with this connection. Final disconnect. Remove from conn manager. May be called multiple times.killOutstandingPackets
private void killOutstandingPackets()Cancel and remove all packets awaiting ack.scheduleDisconnectEvent
private boolean scheduleDisconnectEvent()Schedule the end of the TIME-WAIT state, but only if not previously scheduled. Must call either this or disconnectComplete().- Returns:
- true if a new event was scheduled; false if already scheduled
scheduleConnectionEvent
public void scheduleConnectionEvent(long msToWait) Called fromSchedulerImpl.- Parameters:
msToWait- wait in ms before the ConEvent releases its waiters
getAckDupEvent
Connection.AckDupEvent getAckDupEvent()Schedule an event on our timer. The event should use the no-arg constructor; the pool is set here.- Returns:
- the reusable AckDupEvent for duplicate ACK scheduling
getRemotePeer
Destination of the remote peer.- Returns:
- peer Destination or null if unset
getRemotePeerString
Short label for the remote peer, for log lines.- Returns:
- the first 8 base32 chars of the peer hash in brackets, or "[Unknown]" while the peer is still unset
setRemotePeer
Remote peer of this connection, non-null.- Parameters:
peer- non-null
getRemoteSPK
The key to verify signatures with. The transient SPK if previously received, elsegetRemotePeer().getSigningPublicKey() if previously received, else null.- Returns:
- peer Destination or null if unset
setRemoteTransientSPK
Transient signing public key of the remote peer.- Parameters:
transientSPK- null ok
getSendStreamId
public long getSendStreamId()What stream do we send data to the peer on?- Returns:
- non-global stream sending ID, or 0 if unknown
setSendStreamId
public void setSendStreamId(long id) Stream ID that we send data on.- Parameters:
id- 0 to 0xffffffff- Throws:
IllegalStateException- if already set to nonzero
getReceiveStreamId
public long getReceiveStreamId()The stream ID of a peer connection that sends data to us, or zero if unknown.- Returns:
- receive stream ID, or 0 if unknown
claimSlotRelease
boolean claimSlotRelease()Claim this connection's slot release, once per generation. The winner (first CAS false→true) owns the budget release; every other teardown path — a disconnectAllHard() sweep racing disconnectComplete(), a failed bind's catch racing a later teardown — sees true and must skip its release, or the dest's stream count is decremented for slots it never took.- Returns:
- true if the caller now owns the one release for this generation
resetSlotRelease
void resetSlotRelease()Re-arm the slot-release claim for a new generation, called byConnectionManager.assignReceiveStreamId(Connection, Hash)when a pooled connection binds a fresh reservation token: the next teardown of this object must be able to claim again.setReceiveStreamId
public void setReceiveStreamId(long id) Stream ID that the peer sends data on.- Parameters:
id- 0 to 0xffffffff- Throws:
IllegalStateException- if already set to nonzero
getLastSendTime
public long getLastSendTime()When did we last send anything to the peer?- Returns:
- Last time we sent data
getLastSendId
public long getLastSendId()What was the last packet Id sent to the peer?- Returns:
- The last sent packet ID
getOptions
Retrieve the current ConnectionOptions.- Returns:
- the current ConnectionOptions, non-null
setOptions
ConnectionOptions for this connection.- Parameters:
opts- ConnectionOptions non-null
getConnectionManager
The ConnectionManager that created this connection.- Returns:
- the owning connection manager
getSession
I2P session for this connection.- Returns:
- the I2PSession this connection sends and receives over
getSocket
Socket associated with this connection.- Returns:
- the socket, or null if not yet set
setSocket
Socket associated with this connection.- Parameters:
socket- the socket the connection will read and write on once it is established
getPort
public int getPort()The remote port.- Returns:
- Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
getLocalPort
public int getLocalPort()Local port of this connection.- Returns:
- Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0)
getConnectionError
Connection error message, if any.- Returns:
- error message, or null if no error
setConnectionError
Connection error message.- Parameters:
err- the error message
getLifetime
public long getLifetime()Lifetime of this connection in milliseconds.- Returns:
- connection lifetime in ms
getPacketHandler
Packet handler for this connection.- Returns:
- the packet handler
getLifetimeBytesSent
public long getLifetimeBytesSent()Total bytes sent on this connection.- Returns:
- lifetime bytes sent
getLifetimeBytesReceived
public long getLifetimeBytesReceived()Total bytes received on this connection.- Returns:
- lifetime bytes received
getLifetimeDupMessagesSent
public long getLifetimeDupMessagesSent()Total duplicate messages sent on this connection.- Returns:
- lifetime duplicate messages sent
getLifetimeDupBytesSent
public long getLifetimeDupBytesSent()Total duplicate bytes sent on this connection.- Returns:
- lifetime duplicate bytes sent
getLifetimeDupMessagesReceived
public long getLifetimeDupMessagesReceived()Total duplicate messages received on this connection.- Returns:
- lifetime duplicate messages received
incrementBytesSent
public void incrementBytesSent(int bytes) Increment the lifetime bytes sent counter.- Parameters:
bytes- number of bytes to add
incrementDupMessagesSent
public void incrementDupMessagesSent(int msgs) Increment the lifetime duplicate messages sent counter.- Parameters:
msgs- number of duplicate messages to add
incrementDupBytesSent
public void incrementDupBytesSent(int bytes) Increment the lifetime duplicate bytes sent counter.- Parameters:
bytes- number of duplicate bytes to add
incrementBytesReceived
public void incrementBytesReceived(int bytes) Increment the lifetime bytes received counter.- Parameters:
bytes- number of bytes to add
incrementDupMessagesReceived
public void incrementDupMessagesReceived(int msgs) Increment the lifetime duplicate messages received counter.- Parameters:
msgs- number of duplicate messages to add
getNextSendTime
public long getNextSendTime()Time when the scheduler next want to send a packet, or -1 if never. This should be set when we want to send on timeout, for instance, or want to delay an ACK.- Returns:
- the next time the scheduler will want to send a packet, or -1 if never.
setNextSendTime
public void setNextSendTime(long when) If the next send time is currently >= 0 (i.e. not "never"), this may make the next time sooner but will not make it later. If the next send time is currently < 0 (i.e. "never"), this will set it to the time specified, but not later than options.getSendAckDelay() from now (1000 ms).- Parameters:
when- the desired send time in ms since the epoch, or < 0 for "never"
setChoking
public void setChoking(boolean on) Choking state toward the other side. If on is true or the value has changed, this will call ackImmediately().- Parameters:
on- true for choking
setChoked
public void setChoked(boolean on) Choked state set by the other side.- Parameters:
on- true for choked
isChoked
public boolean isChoked()Whether the other side is choking us.- Returns:
- true if the remote has advertised a zero window
restoredWindowOnUnchoke
static int restoredWindowOnUnchoke(int saved, int ceiling) The window to restore when the remote unchokes us.Restores the pre-choke window and nothing more, capped at the current per-stream ceiling so a window saved under a more generous ceiling cannot jump the queue past what the pipe and heap allow now.
The window deliberately does not consider
ssthresh. Folding it in (the oldmax(saved, ssthresh)) made every unchoke restore the ceiling instead of the demonstrated window, because ssthresh starts atmaxSlowStartWindowand is an asymptotic growth target rather than a capacity measurement. A connection whose window had genuinely collapsed to a handful of packets would be reinflated to the full ceiling on every unchoke, and since a remote only chokes when it is overloaded, that fixed burst fed the loss that caused the next cut.- Parameters:
saved- the window captured when the choke began, always positive at the call siteceiling- the current per-stream window ceiling, in messages- Returns:
- the window to restore, in
[1, ceiling]
getAckedPackets
public long getAckedPackets()How many packets have we sent and the other side has ACKed?- Returns:
- Count of how many packets ACKed.
getCreatedOn
public long getCreatedOn()Timestamp when this connection was created.- Returns:
- router clock time in milliseconds at construction
getCloseSentOn
public long getCloseSentOn()Timestamp when a close was sent.- Returns:
- 0 if not sent
getCloseReceivedOn
public long getCloseReceivedOn()Timestamp when a close was received.- Returns:
- 0 if not received
incrementUnackedPacketsReceived
public void incrementUnackedPacketsReceived()Increment the count of unacked packets received.getUnackedPacketsReceived
public int getUnackedPacketsReceived()Count of unacked packets received.- Returns:
- number of unacked packets received
getUnackedPacketsSent
public int getUnackedPacketsSent()How many packets have we sent but not yet received an ACK for?- Returns:
- Count of packets in-flight.
getWindowLock
- Returns:
- the monitor guarding the outbound window map
outboundPackets
Lazily allocate the sender-side unacked-window map. Caller must hold_outboundPacketsLock.- Returns:
- the non-null window map
outboundSizeLocked
private int outboundSizeLocked()Null-safe window size. Caller must hold_outboundPacketsLock.- Returns:
- 0 when the window was never allocated
pacedQueue
Lazily allocate the paced-send queue. Caller must hold_pacedQueueLock.- Returns:
- the non-null paced queue
getCongestionWindowEnd
public long getCongestionWindowEnd()Congestion window end sequence number.- Returns:
- the congestion window end
setCongestionWindowEnd
public void setCongestionWindowEnd(long endMsg) Congestion window end sequence number.- Parameters:
endMsg- the new congestion window end
getHighestAckedThrough
public long getHighestAckedThrough()Highest outbound packet we have received an ack for.- Returns:
- the message id, -1 until the first ack
getLastActivityOn
public long getLastActivityOn()Timestamp of the last send or receive activity.- Returns:
- the later of last send time and last receive time
congestionOccurred
private void congestionOccurred()Congestion occurred.packetReceived
void packetReceived()Called when a packet is received on this connection. Updates the last received timestamp and resets the activity timer.waitForConnect
void waitForConnect()Wait for the connection to be established, with the option's connectTimeout.waitForConnect
void waitForConnect(int timeoutMs) Wait for the connection to be established, but no longer than timeoutMs. If the connection fails or the timeout is exceeded, sets _connectionError.- Parameters:
timeoutMs- max wait in ms; if <= 0 uses the connection option's connectTimeout
connectWaitLine
The entry a blocking connect writes while it waits, naming how long it is prepared to wait for the handshake to finish.- Parameters:
peer- the remote peer as the bracketed hash truncationtimeoutMs- the wait budget in ms, 0 if the wait is unbounded- Returns:
- the log entry, never null
connectDoneLine
The entry for the end of a blocking connect's wait, whether it ended with a connection or with the timeout giving up.- Parameters:
peer- the remote peer as the bracketed hash truncationelapsedMs- ms spent waitingtimedOut- true if the wait ran out instead of connecting- Returns:
- the log entry, never null
resetActivityTimer
private void resetActivityTimer()Reset activity timer.getInputStream
Input stream that the local peer receives data on.- Returns:
- the inbound message stream, non-null
getOutputStream
Output stream that the local peer sends data to the remote peer on.- Returns:
- the outbound message stream, non-null
streamIds
Receive/send stream IDs as"recv/send","Unknown"when not yet assigned.- Returns:
- the receive and send stream IDs joined with a slash, never null
localTag
First 8 characters of the local destination's base32 hash, so a log line names which of this router's services owns the stream. "unknown" until the session has a local destination.- Returns:
- the 8-character base32 tag, or "unknown" if the session has no local destination yet
toString
sendTLProbe
private void sendTLProbe()Send a tail loss probe: re-enqueue the oldest unacked packet. No congestion control — TLP is a probe, not a confirmed loss.newResendPacketEvent
A new ResendPacketEvent from the pool or a fresh instance.- Parameters:
packet- the packet the event will retransmit- Returns:
- the reused-or-new event, already bound to that packet