mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
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:
@@ -187,6 +187,11 @@ impl Node {
|
||||
|
||||
// Remove link and address mapping
|
||||
self.remove_link(&link_id);
|
||||
// Finding A: drop this peer's inert machine, keyed by the link_id
|
||||
// derived above (a peer's link_id is immutable, so the key never
|
||||
// moved). Keeps peers <-> peer_machines in exact correspondence on
|
||||
// teardown. NEUTRAL — nothing reads peer_machines yet.
|
||||
self.peer_machines.remove(&link_id);
|
||||
if let Some(transport_id) = transport_id {
|
||||
self.cleanup_bootstrap_transport_if_unused(transport_id);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::PeerIdentity;
|
||||
use crate::node::acl::PeerAclContext;
|
||||
use crate::node::reject::{HandshakeReject, RejectReason};
|
||||
use crate::node::{Node, NodeError};
|
||||
use crate::peer::machine::PeerMachine;
|
||||
use crate::peer::{ActivePeer, PeerConnection};
|
||||
use crate::proto::fmp::wire::{Msg1Header, Msg2Header, Msg3Header, build_msg2, build_msg3};
|
||||
use crate::proto::fmp::{
|
||||
@@ -1538,6 +1539,13 @@ impl Node {
|
||||
};
|
||||
let loser_link_id = old_peer.link_id();
|
||||
|
||||
// Finding A: the replaced (losing) peer was established and so
|
||||
// carried a machine keyed by its OWN link_id (loser_link_id);
|
||||
// drop it so no machine orphans when its ActivePeer is removed.
|
||||
// The winning connection's machine is inserted below keyed by
|
||||
// the winner link_id. NEUTRAL — nothing reads peer_machines yet.
|
||||
self.peer_machines.remove(&loser_link_id);
|
||||
|
||||
// Clean up old peer's index from peers_by_index
|
||||
if let (Some(old_tid), Some(old_idx)) =
|
||||
(old_peer.transport_id(), old_peer.our_index())
|
||||
@@ -1595,6 +1603,20 @@ impl Node {
|
||||
);
|
||||
|
||||
self.peers.insert(peer_node_addr, new_peer);
|
||||
// Finding A: populate the inert per-peer machine so every
|
||||
// established peer has exactly one machine, keyed by its
|
||||
// link_id (the winner link here). Nothing drives it yet.
|
||||
self.peer_machines.insert(
|
||||
link_id,
|
||||
PeerMachine::established(
|
||||
link_id,
|
||||
verified_identity,
|
||||
our_index,
|
||||
is_outbound,
|
||||
remote_epoch,
|
||||
current_time_ms,
|
||||
),
|
||||
);
|
||||
self.peers_by_index
|
||||
.insert((transport_id, our_index.as_u32()), peer_node_addr);
|
||||
self.peering
|
||||
@@ -1712,6 +1734,20 @@ impl Node {
|
||||
}
|
||||
|
||||
self.peers.insert(peer_node_addr, new_peer);
|
||||
// Finding A: populate the inert per-peer machine so every
|
||||
// established peer has exactly one machine, keyed by its link_id.
|
||||
// Nothing drives it yet.
|
||||
self.peer_machines.insert(
|
||||
link_id,
|
||||
PeerMachine::established(
|
||||
link_id,
|
||||
verified_identity,
|
||||
our_index,
|
||||
is_outbound,
|
||||
remote_epoch,
|
||||
current_time_ms,
|
||||
),
|
||||
);
|
||||
self.peers_by_index
|
||||
.insert((transport_id, our_index.as_u32()), peer_node_addr);
|
||||
self.peering
|
||||
|
||||
@@ -2388,6 +2388,14 @@ impl Node {
|
||||
}
|
||||
|
||||
/// Remove a peer.
|
||||
///
|
||||
/// Raw `pub` accessor with ZERO callers in the crate (production or test) —
|
||||
/// the production teardown path is [`remove_active_peer`](Self::remove_active_peer),
|
||||
/// which drops the peer's `peer_machines` entry. This helper deliberately
|
||||
/// does NOT touch `peer_machines`: it never runs against an established peer
|
||||
/// in production, so it cannot orphan a machine (Finding A). If it is ever
|
||||
/// wired to remove established peers, mirror the `remove_active_peer` cleanup
|
||||
/// (`self.peer_machines.remove(&peer.link_id())`) here.
|
||||
pub fn remove_peer(&mut self, node_addr: &NodeAddr) -> Option<ActivePeer> {
|
||||
self.peers.remove(node_addr)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user