mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
The per-peer control machine has absorbed every field the pending connection carried. What remained was a struct holding two Noise handles beside a duplicate copy of bookkeeping nobody read. Replace it with a small carrier for the two handles and delete the type. Presence of that carrier, not the state of the handles inside it, is what marks a machine as mid-handshake. The distinction is essential rather than stylistic: a failed handshake drops its initiation handle and is deliberately retained so the stale sweep can reclaim it, and a completed one has its session taken before disposal. Deriving presence from the handles would make both invisible to the sweep, the connection count, and the peering budget at once, leaking the slot permanently. A test drives an empty carrier past every presence predicate and then detaches it, so a future edit cannot quietly couple the two. The remote startup epoch now comes from the surviving carrier, which the handshake operations already wrote at the same two points with the same value. The paired writes onto the pending connection's own bookkeeping had no readers left and are gone. The handshake-phase surface leaves the public API: it was public by accident rather than design, and the machine behind it is crate internal. Callers outside the crate that need a view of pending handshakes go through the operator queries, which are unchanged. ConnectionState::inbound_with_transport loses its last non-test caller with the inbound seed and is marked test-only.
185 lines
6.5 KiB
Rust
185 lines
6.5 KiB
Rust
use super::*;
|
|
use crate::PeerIdentity;
|
|
use crate::peer::machine::HandshakeCrypto;
|
|
use crate::transport::{LinkDirection, ReceivedPacket, TransportAddr, packet_channel};
|
|
use crate::utils::index::SessionIndex;
|
|
use std::time::Duration;
|
|
|
|
mod acl;
|
|
#[cfg(target_os = "linux")]
|
|
mod ble;
|
|
mod bloom;
|
|
mod bloom_poison;
|
|
mod bootstrap;
|
|
mod decrypt_failure;
|
|
mod disconnect;
|
|
mod discovery;
|
|
mod establish_chartests;
|
|
#[cfg(target_os = "linux")]
|
|
mod ethernet;
|
|
mod forwarding;
|
|
mod handshake;
|
|
mod heartbeat;
|
|
mod mmp_chartests;
|
|
mod routing;
|
|
mod session;
|
|
mod spanning_tree;
|
|
mod tcp;
|
|
mod unit;
|
|
|
|
pub(super) fn make_node() -> Node {
|
|
make_node_with(Config::new())
|
|
}
|
|
|
|
/// A test node that reaches `Full` health on `start()`.
|
|
///
|
|
/// A default [`make_node`] configures no transports, so its `start()` now
|
|
/// resolves to `NodeState::Failed` (zero transports up) and
|
|
/// returns `NoOperationalTransports`. Lifecycle-state tests that need a running
|
|
/// node build one with a single loopback UDP transport (ephemeral port) as the
|
|
/// sole configured child — DNS disabled — so bring-up has exactly one
|
|
/// configured child and it comes up (`Full`). Mirrors the udp config in
|
|
/// `test_node_start_does_not_wait_for_nostr_relay_startup`.
|
|
pub(super) fn make_healthy_node() -> Node {
|
|
let mut config = Config::new();
|
|
config.transports.udp = crate::config::TransportInstances::Single(crate::config::UdpConfig {
|
|
bind_addr: Some("127.0.0.1:0".to_string()),
|
|
..Default::default()
|
|
});
|
|
config.dns.enabled = false;
|
|
make_node_with(config)
|
|
}
|
|
|
|
/// Build a test node from an explicit `Config`. Immutable state lives solely in
|
|
/// the shared `NodeContext`, built once at construction — there is no
|
|
/// post-construction field to poke, so set limits/config on the `Config` here.
|
|
pub(super) fn make_node_with(config: Config) -> Node {
|
|
Node::new(config).unwrap()
|
|
}
|
|
|
|
/// Build a test node with an explicit `max_peers` limit (replaces the removed
|
|
/// `set_max_peers` setter; resource limits are immutable post-construction).
|
|
pub(super) fn make_node_with_max_peers(max_peers: usize) -> Node {
|
|
let mut config = Config::new();
|
|
config.node.limits.max_peers = max_peers;
|
|
make_node_with(config)
|
|
}
|
|
|
|
/// Build a test node with an explicit `max_links` limit (replaces the removed
|
|
/// `set_max_links` setter; resource limits are immutable post-construction).
|
|
pub(super) fn make_node_with_max_links(max_links: usize) -> Node {
|
|
let mut config = Config::new();
|
|
config.node.limits.max_links = max_links;
|
|
make_node_with(config)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub(super) fn make_node_addr(val: u8) -> NodeAddr {
|
|
let mut bytes = [0u8; 16];
|
|
bytes[0] = val;
|
|
NodeAddr::from_bytes(bytes)
|
|
}
|
|
|
|
pub(super) fn make_peer_identity() -> PeerIdentity {
|
|
let identity = Identity::generate();
|
|
PeerIdentity::from_pubkey(identity.pubkey())
|
|
}
|
|
|
|
/// A control machine carrying a fresh outbound connection, for tests that
|
|
/// drive one end of a handshake without a whole node behind it. The machine
|
|
/// owns the handshake operations, so it is what the crypto runs on.
|
|
pub(super) fn outbound_leg(
|
|
link_id: LinkId,
|
|
expected_identity: PeerIdentity,
|
|
current_time_ms: u64,
|
|
) -> PeerMachine {
|
|
let mut machine = PeerMachine::new_outbound(link_id, expected_identity, current_time_ms);
|
|
machine.set_leg(HandshakeCrypto::new());
|
|
machine
|
|
}
|
|
|
|
/// The responder twin of [`outbound_leg`].
|
|
pub(super) fn inbound_leg(link_id: LinkId, current_time_ms: u64) -> PeerMachine {
|
|
let mut machine = PeerMachine::new_inbound(link_id, current_time_ms);
|
|
machine.set_leg(HandshakeCrypto::new());
|
|
machine
|
|
}
|
|
|
|
/// Seed a control machine whose leg carries a completed Noise IK handshake.
|
|
///
|
|
/// Returns the peer identity. The leg is outbound, in Complete state, with
|
|
/// session, indices, and transport info set, and is installed on the node
|
|
/// through [`Node::seed_handshake_machine`].
|
|
pub(super) fn seed_completed_connection(
|
|
node: &mut Node,
|
|
link_id: LinkId,
|
|
transport_id: TransportId,
|
|
current_time_ms: u64,
|
|
) -> PeerIdentity {
|
|
let our_index = node.index_allocator.allocate().unwrap();
|
|
seed_completed_connection_with(node, link_id, current_time_ms, |seed| {
|
|
seed.with_our_index(our_index)
|
|
.with_their_index(SessionIndex::new(42))
|
|
.with_transport_id(transport_id)
|
|
.with_source_addr(TransportAddr::from_string("127.0.0.1:5000"))
|
|
})
|
|
}
|
|
|
|
/// [`seed_completed_connection`] with the seed left to the caller, for tests
|
|
/// that need a leg deliberately missing one of the fields promotion requires.
|
|
///
|
|
/// The Noise exchange runs on the already-seeded leg, where it used to run
|
|
/// before the leg was handed over. That reordering is neutral, but not
|
|
/// because the handshake leaves the seeded fields alone —
|
|
/// `receive_handshake_init` does write `expected_identity`. It is neutral
|
|
/// because the only read of `expected_identity` is guarded by `is_outbound`:
|
|
/// an inbound leg takes the `new_inbound` arm whether or not the identity has
|
|
/// been learned, and an outbound leg never runs that method. The remaining
|
|
/// reads (`link_id`, `started_at`, `is_outbound`, `their_index`,
|
|
/// `transport_id`) are genuinely untouched by the handshake.
|
|
pub(super) fn seed_completed_connection_with(
|
|
node: &mut Node,
|
|
link_id: LinkId,
|
|
current_time_ms: u64,
|
|
shape: impl FnOnce(HandshakeSeed) -> HandshakeSeed,
|
|
) -> PeerIdentity {
|
|
let peer_identity_full = Identity::generate();
|
|
// Must use from_pubkey_full to preserve parity for ECDH
|
|
let peer_identity = PeerIdentity::from_pubkey_full(peer_identity_full.pubkey_full());
|
|
|
|
node.seed_handshake_machine(shape(HandshakeSeed::outbound(
|
|
link_id,
|
|
peer_identity,
|
|
current_time_ms,
|
|
)))
|
|
.unwrap();
|
|
|
|
// Run initiator side of handshake
|
|
let our_keypair = node.identity().keypair();
|
|
let startup_epoch = node.startup_epoch();
|
|
let msg1 = node
|
|
.peer_machines
|
|
.get_mut(&link_id)
|
|
.unwrap()
|
|
.start_handshake(our_keypair, startup_epoch, current_time_ms)
|
|
.unwrap();
|
|
|
|
// Run responder side to generate msg2
|
|
let mut resp_conn = inbound_leg(LinkId::new(999), current_time_ms);
|
|
let peer_keypair = peer_identity_full.keypair();
|
|
let mut resp_epoch = [0u8; 8];
|
|
rand::Rng::fill_bytes(&mut rand::rng(), &mut resp_epoch);
|
|
let msg2 = resp_conn
|
|
.receive_handshake_init(peer_keypair, resp_epoch, &msg1, current_time_ms)
|
|
.unwrap();
|
|
|
|
// Complete initiator handshake
|
|
node.peer_machines
|
|
.get_mut(&link_id)
|
|
.unwrap()
|
|
.complete_handshake(&msg2, current_time_ms)
|
|
.unwrap();
|
|
|
|
peer_identity
|
|
}
|