End-to-end session establishment with 100-node bidirectional data test

Implement Noise IK session handshake between arbitrary endpoints, carried
inside SessionDatagram envelopes through the mesh. Sessions use a three-state
machine (Initiating → Established for initiator, Responding → Established
for responder on first DataPacket). Includes session initiation API,
encrypted data transfer, simultaneous initiation tie-break, error signal
handlers (CoordsRequired, PathBroken), and local delivery wiring in the
forwarding handler.

New files: node/session.rs (state types), handlers/session.rs (~500 lines,
all session message handlers + send path), tests/session.rs (11 tests).

100-node integration test establishes sessions across random topology,
sends bidirectional encrypted datagrams through injected TUN channels,
and verifies 200/200 deliveries with 100% session establishment. Reports
routing statistics including avg 4.1 link hops per datagram.

404 tests pass (up from 393).
This commit is contained in:
Johnathan Corgan
2026-02-13 03:13:21 +00:00
parent 85ca8a9ae4
commit 51597b6a5a
8 changed files with 1532 additions and 14 deletions
+125
View File
@@ -0,0 +1,125 @@
//! End-to-end session state.
//!
//! Tracks Noise IK sessions between this node and remote endpoints.
//! Sessions are established via SessionSetup/SessionAck handshake
//! messages carried inside SessionDatagram envelopes through the mesh.
use crate::noise::{HandshakeState, NoiseSession};
use crate::NodeAddr;
use secp256k1::PublicKey;
/// State machine for an end-to-end session.
pub(crate) enum EndToEndState {
/// We initiated: sent SessionSetup with Noise IK msg1, awaiting SessionAck.
Initiating(HandshakeState),
/// We are responding: received msg1, sent SessionAck with msg2.
Responding(HandshakeState),
/// Handshake complete, NoiseSession available for encrypt/decrypt.
Established(NoiseSession),
}
impl EndToEndState {
/// Check if the session is established and ready for data.
pub(crate) fn is_established(&self) -> bool {
matches!(self, EndToEndState::Established(_))
}
/// Check if we are the initiator (waiting for ack).
pub(crate) fn is_initiating(&self) -> bool {
matches!(self, EndToEndState::Initiating(_))
}
/// Check if we are the responder (sent ack, waiting for data).
pub(crate) fn is_responding(&self) -> bool {
matches!(self, EndToEndState::Responding(_))
}
}
/// A single end-to-end session with a remote node.
///
/// The state is wrapped in `Option` to allow taking ownership of the
/// handshake state during transitions without placeholder values.
/// The state is `None` only transiently during handler processing.
pub(crate) struct SessionEntry {
/// Remote node's address (session table key).
remote_addr: NodeAddr,
/// Remote node's static public key (for Noise IK).
remote_pubkey: PublicKey,
/// Current session state. `None` only during state transitions.
state: Option<EndToEndState>,
/// When the session was created (Unix milliseconds).
created_at: u64,
/// Last activity timestamp (Unix milliseconds).
last_activity: u64,
}
impl SessionEntry {
/// Create a new session entry.
pub(crate) fn new(
remote_addr: NodeAddr,
remote_pubkey: PublicKey,
state: EndToEndState,
now_ms: u64,
) -> Self {
Self {
remote_addr,
remote_pubkey,
state: Some(state),
created_at: now_ms,
last_activity: now_ms,
}
}
/// Get the remote node address.
pub(crate) fn remote_addr(&self) -> &NodeAddr {
&self.remote_addr
}
/// Get the remote node's public key.
pub(crate) fn remote_pubkey(&self) -> &PublicKey {
&self.remote_pubkey
}
/// Get the current session state.
pub(crate) fn state(&self) -> &EndToEndState {
self.state.as_ref().expect("session state taken but not restored")
}
/// Get mutable access to the session state.
pub(crate) fn state_mut(&mut self) -> &mut EndToEndState {
self.state.as_mut().expect("session state taken but not restored")
}
/// Replace the session state.
pub(crate) fn set_state(&mut self, state: EndToEndState) {
self.state = Some(state);
}
/// Take the state out, leaving `None`.
///
/// The caller must call `set_state()` to restore a valid state,
/// or discard the entry entirely.
pub(crate) fn take_state(&mut self) -> Option<EndToEndState> {
self.state.take()
}
/// Update the last activity timestamp.
pub(crate) fn touch(&mut self, now_ms: u64) {
self.last_activity = now_ms;
}
/// Check if the session is established.
pub(crate) fn is_established(&self) -> bool {
self.state.as_ref().map_or(false, |s| s.is_established())
}
/// Get creation time.
pub(crate) fn created_at(&self) -> u64 {
self.created_at
}
/// Get last activity time.
pub(crate) fn last_activity(&self) -> u64 {
self.last_activity
}
}