The repo never enforced rustfmt, so formatting had drifted broadly. This is a
single mechanical `cargo fmt` pass over the whole crate (no behavioral change;
lib suite green, 493 passed). Going forward fmt should be enforced (planned CI
fmt --check step). Part of the 0.6.1 hygiene pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The friends-only presence listener (W7) must answer pings while the app is
open, whether or not we're in a call — but a node id has exactly one live
endpoint instance (proven by the dual-endpoint spike: two endpoints sharing a
SecretKey collide, all inbound connections land on one and the other's ALPN
fails the QUIC handshake). So the listener can't get its own endpoint; the
whole app must share one persistent endpoint. Today the core rebuilds the
endpoint+gossip+router on every Join and tears them down on leave, so there's
nothing alive between calls.
B1 hoists those durable pieces to the app lifetime (no new behavior):
- New persistent `NetStack` (endpoint + gossip + Router) built once at startup
under the RelayNoDiscovery default (relay reachability, no DNS beacon);
`online()` is backgrounded so launch isn't blocked.
- New persistent `AudioRouter` (src/network/iroh_impl.rs) replaces the
per-session `AudioProtocol`: it's registered once on the single Router and
delegates each inbound audio connection to whatever session `Shared` is bound
(`bind` on join, `clear` on leave), dropping links when idle. `IrohTransport::
new` now returns just `Self`.
- Join reuses `net.endpoint`/`net.gossip` and only subscribes its gossip topic +
binds the audio router; Leave clears the router but keeps the endpoint up.
- `SetNetworkMode`/`RegenerateIdentity` rebuild the stack immediately when idle,
else defer to the next Leave/Join (preserves "applies on next join"), and the
existing session is always torn down before any rebuild closes the endpoint.
Tests/loopback updated for the new transport API. 256 lib + 6 reconnect + 4
loopback + 2 ignored real-endpoint tests green, clippy --all-targets clean,
release builds. NOT yet 2-machine field-verified — that regression is the gate
before this merges to main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two reconnect-eviction edges were logically handled but had no explicit
assertion:
- A graceful ConnEvent::Left must emit PeerLeft *immediately* (not wait on
the grace timer). New graceful_leave_evicts_immediately asserts the leave
lands well inside the grace window.
- After a grace-timer eviction scrubs the peer from seen_connected, a later
rejoin's initial dial must be a fresh first-dial, not a reconnect that
arms a new eviction. New rejoin_after_grace_eviction_dials_cleanly covers
this (distinct from the existing graceful-leave scrub path).
Also reorder the eviction task to scrub timers/seen_connected before
emitting PeerConnectionFailed, so an observer (or a redial racing the
eviction) always sees clean internal state — this also makes the new
rejoin-after-eviction test deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the conn-event handling into a testable ConnEventHandler (grace window
as a field) and add tests/reconnect_eviction.rs covering:
- second outage after a reconnect still evicts (the bug fixed in bc1a0a2;
proven to fail when the transport-arming is disabled)
- a reconnect within grace is not evicted
- a first-ever dial is not given an eviction timer
- a graceful leave scrubs seen_connected so a later rejoin dials cleanly
Behavior-preserving refactor: the conn-event task now builds a ConnEventHandler
and forwards each event to it; arm_grace_timer takes the grace Duration as a
param (production passes RECONNECT_GRACE). All existing transport/reconnect
integration tests still pass; clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>