node: populate the per-peer machine map at establishment

Insert a per-peer machine into peer_machines at each promote site and
remove it on teardown, so every established peer has exactly one machine
entry keyed by its link id. Nothing drives the machine yet — the entry
is inert — but this correspondence is what the rekey and liveness-reap
folds depend on.

Inserts pair with the two promote_connection arms (normal promote and
cross-connection-won); removes pair with remove_active_peer and the
cross-connection loser teardown. A new PeerMachine::established
constructor parks the machine in the post-handshake Established state.
Behavior is unchanged: nothing reads the map on a live path.
This commit is contained in:
Johnathan Corgan
2026-07-13 20:33:29 +00:00
parent 42de582ef0
commit 5498b2a6a3
4 changed files with 120 additions and 0 deletions
+71
View File
@@ -448,6 +448,53 @@ impl PeerMachine {
}
}
/// New machine for an ALREADY-established peer: the post-handshake state a
/// promoted peer occupies before any rekey. M3 inserts one of these into
/// `Node.peer_machines` at each `promote_connection` establishment site so
/// every established peer has exactly one machine keyed by its `LinkId`
/// (Finding A). The machine is **inert** — nothing drives it yet — and is
/// parked at [`PeerState::Established`] so a later reap sees
/// [`is_established_context`](Self::is_established_context) true and a later
/// rekey step finds it. `our_index` is the peer's msg1-allocated session
/// index; `remote_epoch` is the crystallized peer's startup epoch.
pub(crate) fn established(
link: LinkId,
identity: PeerIdentity,
our_index: SessionIndex,
is_outbound: bool,
remote_epoch: Option<[u8; 8]>,
now: u64,
) -> Self {
let addr = *identity.node_addr();
let mut conn = if is_outbound {
ConnectionState::outbound(link, identity, now)
} else {
ConnectionState::inbound(link, now)
};
conn.set_our_index(our_index);
conn.set_remote_epoch(remote_epoch);
Self {
state: PeerState::Established { addr },
link,
identity: Some(identity),
node_addr: Some(addr),
conn,
remote_epoch,
rekey_in_progress: false,
rekey_our_index: None,
rekey_msg1: None,
rekey_resend_count: 0,
last_peer_rekey_ms: 0,
rekey_msg3_pending: false,
session_established_at_ms: now,
authenticated_at_ms: now,
rekey_jitter_secs: 0,
last_heartbeat_sent_ms: 0,
our_index: Some(our_index),
draining_index: None,
}
}
/// Current lifecycle state.
pub(crate) fn state(&self) -> PeerState {
self.state
@@ -1267,6 +1314,30 @@ mod tests {
}
}
// ---- Test 0: established constructor (Finding A populate) --------------
#[test]
fn established_constructor_yields_established_context() {
let id = peer_identity();
let addr = *id.node_addr();
let idx = SessionIndex::new(0x4242);
let m = PeerMachine::established(
LinkId::new(7),
id,
idx,
/* is_outbound */ true,
None,
1_234,
);
// Parked at Established with the crystallized address + index visible,
// so a later reap's `is_established_context` and a later rekey both
// find it.
assert_eq!(m.state(), PeerState::Established { addr });
assert!(m.is_established_context());
assert_eq!(m.addr(), Some(addr));
assert_eq!(m.our_index(), Some(idx));
}
// ---- Test 1: rekey initiator cutover ----------------------------------
#[test]
fn rekey_initiator_cutover() {