use super::*;
use crate::noise::HandshakeState;
use crate::protocol::SessionMsg3;
#[test]
fn test_session_3node_forwarded_handshake() {
run_large_stack_async_test("fips-forwarded-handshake", || async {
session_3node_forwarded_handshake().await;
});
}
async fn session_3node_forwarded_handshake() {
let edges = vec![(0, 1), (1, 2)];
let mut nodes = run_tree_test(3, &edges, false).await;
verify_tree_convergence(&nodes);
populate_all_coord_caches(&mut nodes);
let node0_addr = *nodes[0].node.node_addr();
let node2_addr = *nodes[2].node.node_addr();
let node2_pubkey = nodes[2].node.identity().pubkey_full();
nodes[0]
.node
.initiate_session(node2_addr, node2_pubkey)
.await
.expect("initiate_session failed");
assert!(wait_process_packets_for_node(&mut nodes, 1).await > 0);
assert!(wait_process_packets_for_node(&mut nodes, 2).await > 0);
assert!(
nodes[2].node.get_session(&node0_addr).is_some(),
"Node 2 should have a session entry for Node 0"
);
assert!(
nodes[2]
.node
.get_session(&node0_addr)
.unwrap()
.is_awaiting_msg3()
);
assert!(wait_process_packets_for_node(&mut nodes, 1).await > 0);
assert!(wait_process_packets_for_node(&mut nodes, 0).await > 0);
assert!(
nodes[0]
.node
.get_session(&node2_addr)
.unwrap()
.is_established()
);
assert!(wait_process_packets_for_node(&mut nodes, 1).await > 0);
assert!(wait_process_packets_for_node(&mut nodes, 2).await > 0);
assert!(
nodes[2]
.node
.get_session(&node0_addr)
.unwrap()
.is_established()
);
assert_eq!(
nodes[1].node.session_count(),
0,
"Transit node should have no sessions"
);
cleanup_nodes(&mut nodes).await;
}
#[tokio::test]
async fn test_routed_session_establishes_through_authenticated_previous_hop_without_reverse_route()
{
let mut nodes = run_tree_test(2, &[(0, 1)], false).await;
verify_tree_convergence(&nodes);
nodes[1].node.config.node.routing.mode = RoutingMode::ReplyLearned;
let initiator = Node::new(Config::new()).expect("initiator node");
let initiator_addr = *initiator.node_addr();
let responder_addr = *nodes[1].node.node_addr();
let mut handshake = HandshakeState::new_xk_initiator(
initiator.identity().keypair(),
nodes[1].node.identity().pubkey_full(),
);
handshake.set_local_epoch([0x01; 8]);
let msg1 = handshake
.write_xk_message_1()
.expect("initiator should generate XK msg1");
let setup = SessionSetup::new(
initiator.tree_state().my_coords().clone(),
nodes[1].node.tree_state().my_coords().clone(),
)
.with_handshake(msg1);
let datagram = SessionDatagram::new(initiator_addr, responder_addr, setup.encode());
let encoded = datagram.encode();
assert!(
nodes[1].node.find_next_hop(&initiator_addr).is_none(),
"test requires the responder to have no ordinary route to the initiator"
);
nodes[0]
.node
.send_dataplane_fmp_link_plaintext(&responder_addr, &encoded, false)
.await
.expect("transit should send routed SessionSetup to responder");
assert!(
wait_process_packets_for_node(&mut nodes, 1).await > 0,
"routed SessionSetup should reach the responder over FMP"
);
assert!(
nodes[1]
.node
.get_session(&initiator_addr)
.is_some_and(|session| session.is_awaiting_msg3()),
"responder should return SessionAck through the authenticated previous hop"
);
assert!(
wait_process_packets_for_node(&mut nodes, 0).await > 0,
"SessionAck should reach the authenticated previous hop"
);
let ack_payload = nodes[1]
.node
.get_session(&initiator_addr)
.and_then(|session| session.handshake_payload())
.expect("responder should retain SessionAck for resend");
let ack = SessionAck::decode(&ack_payload[FSP_COMMON_PREFIX_SIZE..])
.expect("stored SessionAck should decode");
handshake
.read_xk_message_2(&ack.handshake_payload)
.expect("initiator should process XK msg2");
let msg3 = handshake
.write_xk_message_3()
.expect("initiator should generate XK msg3");
let datagram = SessionDatagram::new(
initiator_addr,
responder_addr,
SessionMsg3::new(msg3).encode(),
);
nodes[0]
.node
.send_dataplane_fmp_link_plaintext(&responder_addr, &datagram.encode(), false)
.await
.expect("transit should send routed SessionMsg3 to responder");
assert!(
wait_process_packets_for_node(&mut nodes, 1).await > 0,
"routed SessionMsg3 should reach the responder over FMP"
);
let session = nodes[1]
.node
.get_session(&initiator_addr)
.expect("responder should retain the authenticated session");
assert!(
session.is_established(),
"responder session should establish"
);
assert_eq!(
session
.remote_identity()
.map(|identity| *identity.node_addr()),
Some(initiator_addr),
"Noise msg3 must authenticate the claimed source NodeAddr"
);
let transit_addr = *nodes[0].node.node_addr();
assert_eq!(
nodes[1]
.node
.find_next_hop(&initiator_addr)
.map(|peer| *peer.node_addr()),
Some(transit_addr),
"authenticated msg3 should install the reverse route"
);
assert_eq!(
nodes[1].node.dataplane.fsp_owner_next_hop(&initiator_addr),
Some(transit_addr),
"responder dataplane owner should wrap replies through the authenticated previous hop"
);
cleanup_nodes(&mut nodes).await;
}
#[tokio::test]
async fn test_session_initiate_idempotent() {
let edges = vec![(0, 1)];
let mut nodes = run_tree_test(2, &edges, false).await;
verify_tree_convergence(&nodes);
populate_all_coord_caches(&mut nodes);
let node1_addr = *nodes[1].node.node_addr();
let node1_pubkey = nodes[1].node.identity().pubkey_full();
nodes[0]
.node
.initiate_session(node1_addr, node1_pubkey)
.await
.unwrap();
assert_eq!(nodes[0].node.session_count(), 1);
nodes[0]
.node
.initiate_session(node1_addr, node1_pubkey)
.await
.unwrap();
assert_eq!(nodes[0].node.session_count(), 1);
cleanup_nodes(&mut nodes).await;
}
#[tokio::test]
async fn test_session_ack_for_unknown_session() {
let edges = vec![(0, 1)];
let mut nodes = run_tree_test(2, &edges, false).await;
verify_tree_convergence(&nodes);
let node0_addr = *nodes[0].node.node_addr();
let node1_addr = *nodes[1].node.node_addr();
let src_coords = nodes[1].node.tree_state().my_coords().clone();
let dest_coords = nodes[0].node.tree_state().my_coords().clone();
let ack = SessionAck::new(src_coords, dest_coords).with_handshake(vec![0u8; 57]);
let datagram = SessionDatagram::new(node1_addr, node0_addr, ack.encode());
let encoded = datagram.encode();
nodes[1]
.node
.send_dataplane_fmp_link_plaintext(&node0_addr, &encoded, false)
.await
.unwrap();
tokio::time::sleep(Duration::from_millis(20)).await;
process_available_packets(&mut nodes).await;
assert_eq!(nodes[0].node.session_count(), 0);
cleanup_nodes(&mut nodes).await;
}