mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 00:26:59 +00:00
Switch FMP handshake from Noise IK to XX with version negotiation
Replace the 2-message IK handshake with a 3-message XX handshake for FMP link establishment. XX requires no prior knowledge of the peer's static key — both identities are revealed during the handshake (responder in msg2, initiator in msg3). This is the foundation for the forklift upgrade that enables rolling protocol upgrades. Changes: - Noise XX state machine alongside IK/XK (8 unit tests) - Protocol negotiation payload codec: format byte, packed version min/max, 64-bit feature bitfield, TLV extensions (11 unit tests) - FMP wire format version 0→1, msg3 header/builder, TCP stream framing - FMP handshake switched to XX: PeerConnection 3-message flow, handle_msg1 simplified (no identity), handle_msg2 sends msg3 and promotes initiator, new handle_msg3 promotes responder with restart/rekey/cross-connection detection - Rekey handshake switched to XX with negotiation payload hash chain fix (decrypt-and-discard in complete_rekey_msg2/msg3) - Negotiation payload in msg2/msg3 (FMP version [1,1], features=0) - Debug logging for handshake promotion paths - Integration test convergence timeouts adjusted for extra round-trip Squashed commits: - Add Noise XX state machine alongside IK/XK - Add protocol negotiation payload codec - FMP wire format prep: version 1, msg3 header support - Switch FMP handshake from Noise IK to XX - Increase convergence timeouts for XX 3-message handshake - Fix negotiation hash chain desync in rekey handshake
This commit is contained in:
+122
-20
@@ -5,10 +5,10 @@
|
||||
|
||||
use crate::bloom::BloomFilter;
|
||||
use crate::mmp::{MmpConfig, MmpPeerState};
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::noise::{HandshakeState as NoiseHandshakeState, NoiseError, NoiseSession};
|
||||
use crate::transport::{LinkId, LinkStats, TransportAddr, TransportId};
|
||||
use crate::tree::{ParentDeclaration, TreeCoordinate};
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::{FipsAddress, NodeAddr, PeerIdentity};
|
||||
use secp256k1::XOnlyPublicKey;
|
||||
use std::fmt;
|
||||
@@ -32,10 +32,7 @@ pub enum ConnectivityState {
|
||||
impl ConnectivityState {
|
||||
/// Check if the peer is usable for sending traffic.
|
||||
pub fn can_send(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ConnectivityState::Connected | ConnectivityState::Stale
|
||||
)
|
||||
matches!(self, ConnectivityState::Connected | ConnectivityState::Stale)
|
||||
}
|
||||
|
||||
/// Check if this is a terminal state requiring cleanup.
|
||||
@@ -182,6 +179,11 @@ pub struct ActivePeer {
|
||||
rekey_msg1: Option<Vec<u8>>,
|
||||
/// In-progress rekey: next resend timestamp (Unix ms).
|
||||
rekey_msg1_next_resend: u64,
|
||||
// === Rekey Responder State (XX pattern) ===
|
||||
/// In-progress rekey responder: Noise handshake state awaiting msg3.
|
||||
rekey_responder_handshake: Option<NoiseHandshakeState>,
|
||||
/// In-progress rekey responder: our new session index.
|
||||
rekey_responder_our_index: Option<SessionIndex>,
|
||||
}
|
||||
|
||||
impl ActivePeer {
|
||||
@@ -233,6 +235,8 @@ impl ActivePeer {
|
||||
rekey_our_index: None,
|
||||
rekey_msg1: None,
|
||||
rekey_msg1_next_resend: 0,
|
||||
rekey_responder_handshake: None,
|
||||
rekey_responder_our_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +317,8 @@ impl ActivePeer {
|
||||
rekey_our_index: None,
|
||||
rekey_msg1: None,
|
||||
rekey_msg1_next_resend: 0,
|
||||
rekey_responder_handshake: None,
|
||||
rekey_responder_our_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,7 +753,12 @@ impl ActivePeer {
|
||||
// === Filter Updates ===
|
||||
|
||||
/// Update peer's inbound filter.
|
||||
pub fn update_filter(&mut self, filter: BloomFilter, sequence: u64, current_time_ms: u64) {
|
||||
pub fn update_filter(
|
||||
&mut self,
|
||||
filter: BloomFilter,
|
||||
sequence: u64,
|
||||
current_time_ms: u64,
|
||||
) {
|
||||
self.inbound_filter = Some(filter);
|
||||
self.filter_sequence = sequence;
|
||||
self.filter_received_at = current_time_ms;
|
||||
@@ -962,11 +973,12 @@ impl ActivePeer {
|
||||
self.rekey_msg1_next_resend = 0;
|
||||
self.rekey_in_progress = false;
|
||||
// Return whichever index needs freeing
|
||||
self.rekey_our_index.take().or_else(|| {
|
||||
self.pending_new_session = None;
|
||||
self.pending_their_index = None;
|
||||
self.pending_our_index.take()
|
||||
})
|
||||
self.rekey_our_index.take()
|
||||
.or_else(|| {
|
||||
self.pending_new_session = None;
|
||||
self.pending_their_index = None;
|
||||
self.pending_our_index.take()
|
||||
})
|
||||
}
|
||||
|
||||
// === Rekey Handshake State (Initiator) ===
|
||||
@@ -991,33 +1003,92 @@ impl ActivePeer {
|
||||
self.rekey_our_index
|
||||
}
|
||||
|
||||
/// Complete the rekey by processing msg2 (initiator side).
|
||||
/// Complete the rekey by processing msg2 (initiator side, XX pattern).
|
||||
///
|
||||
/// Takes the stored handshake state, reads msg2, and returns the
|
||||
/// completed NoiseSession. Clears the handshake-related fields but
|
||||
/// leaves rekey_our_index for set_pending_session to use.
|
||||
pub fn complete_rekey_msg2(&mut self, msg2_bytes: &[u8]) -> Result<NoiseSession, NoiseError> {
|
||||
let mut hs = self
|
||||
.rekey_handshake
|
||||
/// Takes the stored handshake state, reads XX msg2, generates XX msg3,
|
||||
/// and returns (msg3_bytes, completed NoiseSession). Clears the
|
||||
/// handshake-related fields but leaves rekey_our_index for
|
||||
/// set_pending_session to use.
|
||||
pub fn complete_rekey_msg2(
|
||||
&mut self,
|
||||
msg2_bytes: &[u8],
|
||||
) -> Result<(Vec<u8>, NoiseSession), NoiseError> {
|
||||
let mut hs = self.rekey_handshake
|
||||
.take()
|
||||
.ok_or_else(|| NoiseError::WrongState {
|
||||
expected: "rekey handshake in progress".to_string(),
|
||||
got: "no handshake state".to_string(),
|
||||
})?;
|
||||
|
||||
hs.read_message_2(msg2_bytes)?;
|
||||
// Split msg2 into base XX part and any extra (negotiation payload)
|
||||
let base_size = crate::noise::XX_HANDSHAKE_MSG2_SIZE;
|
||||
let (base_msg2, extra) = if msg2_bytes.len() > base_size {
|
||||
(&msg2_bytes[..base_size], Some(&msg2_bytes[base_size..]))
|
||||
} else {
|
||||
(msg2_bytes, None)
|
||||
};
|
||||
|
||||
hs.read_xx_message_2(base_msg2)?;
|
||||
|
||||
// Must decrypt negotiation payload (if present) to keep hash chain
|
||||
// in sync, even though rekey doesn't use the negotiation result.
|
||||
if let Some(encrypted_neg) = extra {
|
||||
let _ = hs.decrypt_payload(encrypted_neg)?;
|
||||
}
|
||||
|
||||
let msg3 = hs.write_xx_message_3()?;
|
||||
let session = hs.into_session()?;
|
||||
|
||||
// Clear msg1 resend state
|
||||
self.rekey_msg1 = None;
|
||||
self.rekey_msg1_next_resend = 0;
|
||||
|
||||
Ok((msg3, session))
|
||||
}
|
||||
|
||||
/// Complete the rekey by processing msg3 (responder side, XX pattern).
|
||||
///
|
||||
/// Takes the stored responder handshake state, reads XX msg3, and returns
|
||||
/// the completed NoiseSession.
|
||||
pub fn complete_rekey_msg3(
|
||||
&mut self,
|
||||
msg3_bytes: &[u8],
|
||||
) -> Result<NoiseSession, NoiseError> {
|
||||
let mut hs = self.rekey_responder_handshake
|
||||
.take()
|
||||
.ok_or_else(|| NoiseError::WrongState {
|
||||
expected: "rekey responder handshake awaiting msg3".to_string(),
|
||||
got: "no responder handshake state".to_string(),
|
||||
})?;
|
||||
|
||||
// Split msg3 into base XX part and any extra (negotiation payload)
|
||||
let base_size = crate::noise::XX_HANDSHAKE_MSG3_SIZE;
|
||||
let (base_msg3, extra) = if msg3_bytes.len() > base_size {
|
||||
(&msg3_bytes[..base_size], Some(&msg3_bytes[base_size..]))
|
||||
} else {
|
||||
(msg3_bytes, None)
|
||||
};
|
||||
|
||||
hs.read_xx_message_3(base_msg3)?;
|
||||
|
||||
// Must decrypt negotiation payload (if present) to keep hash chain
|
||||
// in sync, even though rekey doesn't use the negotiation result.
|
||||
if let Some(encrypted_neg) = extra {
|
||||
let _ = hs.decrypt_payload(encrypted_neg)?;
|
||||
}
|
||||
|
||||
let session = hs.into_session()?;
|
||||
|
||||
self.rekey_responder_our_index = None;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
/// Check if msg1 needs resending.
|
||||
pub fn needs_msg1_resend(&self, now_ms: u64) -> bool {
|
||||
self.rekey_in_progress && self.rekey_msg1.is_some() && now_ms >= self.rekey_msg1_next_resend
|
||||
self.rekey_in_progress
|
||||
&& self.rekey_msg1.is_some()
|
||||
&& now_ms >= self.rekey_msg1_next_resend
|
||||
}
|
||||
|
||||
/// Get msg1 bytes for resend (without consuming).
|
||||
@@ -1029,6 +1100,37 @@ impl ActivePeer {
|
||||
pub fn set_msg1_next_resend(&mut self, next_ms: u64) {
|
||||
self.rekey_msg1_next_resend = next_ms;
|
||||
}
|
||||
|
||||
// === Rekey Responder State (XX pattern) ===
|
||||
|
||||
/// Whether this peer has a rekey responder handshake awaiting msg3.
|
||||
pub fn has_rekey_responder_handshake(&self) -> bool {
|
||||
self.rekey_responder_handshake.is_some()
|
||||
}
|
||||
|
||||
/// Get the rekey responder our_index.
|
||||
pub fn rekey_responder_our_index(&self) -> Option<SessionIndex> {
|
||||
self.rekey_responder_our_index
|
||||
}
|
||||
|
||||
/// Store rekey responder handshake state after sending msg2.
|
||||
///
|
||||
/// Called when processing a rekey msg1 from the peer. The handshake
|
||||
/// state is held here until msg3 arrives to complete the rekey.
|
||||
pub fn set_rekey_responder_state(
|
||||
&mut self,
|
||||
handshake: NoiseHandshakeState,
|
||||
our_index: SessionIndex,
|
||||
) {
|
||||
self.rekey_responder_handshake = Some(handshake);
|
||||
self.rekey_responder_our_index = Some(our_index);
|
||||
}
|
||||
|
||||
/// Clear rekey responder state (on failure or abandonment).
|
||||
pub fn clear_rekey_responder(&mut self) {
|
||||
self.rekey_responder_handshake = None;
|
||||
self.rekey_responder_our_index = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+165
-68
@@ -1,21 +1,21 @@
|
||||
//! Peer Connection (Handshake Phase)
|
||||
//!
|
||||
//! Represents an in-progress connection before authentication completes.
|
||||
//! PeerConnection tracks the Noise IK handshake state and transitions to
|
||||
//! PeerConnection tracks the Noise XX handshake state and transitions to
|
||||
//! ActivePeer upon successful authentication.
|
||||
|
||||
use crate::PeerIdentity;
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::noise::{self, NoiseError, NoiseSession};
|
||||
use crate::transport::{LinkDirection, LinkId, LinkStats, TransportAddr, TransportId};
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::PeerIdentity;
|
||||
use secp256k1::Keypair;
|
||||
use std::fmt;
|
||||
|
||||
/// Handshake protocol state machine.
|
||||
///
|
||||
/// For Noise IK pattern:
|
||||
/// - Initiator: Initial → SentMsg1 → Complete
|
||||
/// - Responder: Initial → ReceivedMsg1 → Complete
|
||||
/// For Noise XX pattern:
|
||||
/// - Initiator: Initial → SentMsg1 → Complete (after processing msg2 + sending msg3)
|
||||
/// - Responder: Initial → ReceivedMsg1 → Complete (after processing msg3)
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum HandshakeState {
|
||||
/// Initial state, ready to start handshake.
|
||||
@@ -398,8 +398,8 @@ impl PeerConnection {
|
||||
|
||||
/// Start the handshake as initiator and generate message 1.
|
||||
///
|
||||
/// For outbound connections only. Returns the handshake message to send.
|
||||
/// The epoch is our startup epoch, encrypted into msg1 for restart detection.
|
||||
/// For outbound connections only. Returns the Noise XX msg1 bytes.
|
||||
/// XX msg1 is ephemeral-only (33 bytes) — no identity or epoch.
|
||||
pub fn start_handshake(
|
||||
&mut self,
|
||||
our_keypair: Keypair,
|
||||
@@ -420,15 +420,10 @@ impl PeerConnection {
|
||||
});
|
||||
}
|
||||
|
||||
let remote_static = self
|
||||
.expected_identity
|
||||
.as_ref()
|
||||
.expect("outbound must have expected identity")
|
||||
.pubkey_full();
|
||||
|
||||
let mut hs = noise::HandshakeState::new_initiator(our_keypair, remote_static);
|
||||
// XX initiator: no remote static needed upfront
|
||||
let mut hs = noise::HandshakeState::new_xx_initiator(our_keypair);
|
||||
hs.set_local_epoch(epoch);
|
||||
let msg1 = hs.write_message_1()?;
|
||||
let msg1 = hs.write_xx_message_1()?;
|
||||
|
||||
self.noise_handshake = Some(hs);
|
||||
self.handshake_state = HandshakeState::SentMsg1;
|
||||
@@ -439,13 +434,19 @@ impl PeerConnection {
|
||||
|
||||
/// Initialize responder and process incoming message 1.
|
||||
///
|
||||
/// For inbound connections only. Returns the handshake message 2 to send.
|
||||
/// The epoch is our startup epoch, encrypted into msg2 for restart detection.
|
||||
/// For inbound connections only. Returns the Noise XX msg2 bytes.
|
||||
/// XX: identity is NOT learned from msg1 (only ephemeral exchange).
|
||||
/// The responder learns the initiator's identity from msg3.
|
||||
/// The handshake remains in ReceivedMsg1 state (not Complete).
|
||||
///
|
||||
/// If `negotiation_payload` is provided, it is encrypted and appended
|
||||
/// to the returned msg2 bytes.
|
||||
pub fn receive_handshake_init(
|
||||
&mut self,
|
||||
our_keypair: Keypair,
|
||||
epoch: [u8; 8],
|
||||
message: &[u8],
|
||||
negotiation_payload: Option<&[u8]>,
|
||||
current_time_ms: u64,
|
||||
) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.direction != LinkDirection::Inbound {
|
||||
@@ -462,41 +463,45 @@ impl PeerConnection {
|
||||
});
|
||||
}
|
||||
|
||||
let mut hs = noise::HandshakeState::new_responder(our_keypair);
|
||||
let mut hs = noise::HandshakeState::new_xx_responder(our_keypair);
|
||||
hs.set_local_epoch(epoch);
|
||||
|
||||
// Process message 1 (this reveals the initiator's identity and epoch)
|
||||
hs.read_message_1(message)?;
|
||||
// Process XX message 1 (ephemeral only — no identity learned)
|
||||
hs.read_xx_message_1(message)?;
|
||||
|
||||
// Extract the discovered identity
|
||||
let remote_static = *hs
|
||||
.remote_static()
|
||||
.expect("remote static available after msg1");
|
||||
self.expected_identity = Some(PeerIdentity::from_pubkey_full(remote_static));
|
||||
// Generate XX message 2 (sends our static + epoch)
|
||||
let mut msg2 = hs.write_xx_message_2()?;
|
||||
|
||||
// Capture remote epoch from msg1
|
||||
self.remote_epoch = hs.remote_epoch();
|
||||
// Append encrypted negotiation payload if provided
|
||||
if let Some(payload) = negotiation_payload {
|
||||
let encrypted = hs.encrypt_payload(payload)?;
|
||||
msg2.extend_from_slice(&encrypted);
|
||||
}
|
||||
|
||||
// Generate message 2
|
||||
let msg2 = hs.write_message_2()?;
|
||||
|
||||
// Handshake is complete for responder
|
||||
let session = hs.into_session()?;
|
||||
self.noise_session = Some(session);
|
||||
self.handshake_state = HandshakeState::Complete;
|
||||
// XX: handshake NOT complete yet — need msg3.
|
||||
// Keep the handshake state for complete_handshake_msg3().
|
||||
self.noise_handshake = Some(hs);
|
||||
self.handshake_state = HandshakeState::ReceivedMsg1;
|
||||
self.last_activity = current_time_ms;
|
||||
|
||||
Ok(msg2)
|
||||
}
|
||||
|
||||
/// Complete the handshake by processing message 2.
|
||||
/// Complete the handshake by processing message 2 and generating message 3.
|
||||
///
|
||||
/// For outbound connections only (initiator completing handshake).
|
||||
/// For outbound connections only (initiator). Processes the responder's
|
||||
/// msg2 (learning their identity and epoch), then generates msg3.
|
||||
/// Returns the Noise XX msg3 bytes to send.
|
||||
///
|
||||
/// If `negotiation_payload` is provided, it is encrypted and appended
|
||||
/// to the returned msg3 bytes. If the received msg2 contains a negotiation
|
||||
/// payload (bytes beyond the base XX msg2), it is decrypted and returned.
|
||||
pub fn complete_handshake(
|
||||
&mut self,
|
||||
message: &[u8],
|
||||
negotiation_payload: Option<&[u8]>,
|
||||
current_time_ms: u64,
|
||||
) -> Result<(), NoiseError> {
|
||||
) -> Result<(Vec<u8>, Option<Vec<u8>>), NoiseError> {
|
||||
if self.handshake_state != HandshakeState::SentMsg1 {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "sent_msg1 state".to_string(),
|
||||
@@ -509,17 +514,109 @@ impl PeerConnection {
|
||||
.take()
|
||||
.expect("noise handshake must exist in SentMsg1 state");
|
||||
|
||||
hs.read_message_2(message)?;
|
||||
// Split msg2 into base XX part and optional negotiation
|
||||
let base_size = noise::XX_HANDSHAKE_MSG2_SIZE;
|
||||
let (base_msg2, extra) = if message.len() > base_size {
|
||||
(&message[..base_size], Some(&message[base_size..]))
|
||||
} else {
|
||||
(message, None)
|
||||
};
|
||||
|
||||
// Process XX msg2 (learns responder identity + epoch)
|
||||
hs.read_xx_message_2(base_msg2)?;
|
||||
|
||||
// Decrypt negotiation payload from msg2 if present
|
||||
let received_negotiation = if let Some(encrypted) = extra {
|
||||
Some(hs.decrypt_payload(encrypted)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Learn responder identity from msg2
|
||||
let remote_static = *hs
|
||||
.remote_static()
|
||||
.expect("remote static available after XX msg2");
|
||||
self.expected_identity = Some(PeerIdentity::from_pubkey_full(remote_static));
|
||||
|
||||
// Capture remote epoch from msg2
|
||||
self.remote_epoch = hs.remote_epoch();
|
||||
|
||||
// Generate XX msg3
|
||||
let mut msg3 = hs.write_xx_message_3()?;
|
||||
|
||||
// Append encrypted negotiation payload if provided
|
||||
if let Some(payload) = negotiation_payload {
|
||||
let encrypted = hs.encrypt_payload(payload)?;
|
||||
msg3.extend_from_slice(&encrypted);
|
||||
}
|
||||
|
||||
// Handshake complete for initiator
|
||||
let session = hs.into_session()?;
|
||||
self.noise_session = Some(session);
|
||||
self.handshake_state = HandshakeState::Complete;
|
||||
self.last_activity = current_time_ms;
|
||||
|
||||
Ok(())
|
||||
Ok((msg3, received_negotiation))
|
||||
}
|
||||
|
||||
/// Complete the responder handshake by processing message 3.
|
||||
///
|
||||
/// For inbound connections only (responder). Processes the initiator's
|
||||
/// msg3, learning their identity and epoch.
|
||||
///
|
||||
/// If the msg3 contains a negotiation payload (bytes beyond base XX msg3),
|
||||
/// it is decrypted and returned.
|
||||
pub fn complete_handshake_msg3(
|
||||
&mut self,
|
||||
message: &[u8],
|
||||
current_time_ms: u64,
|
||||
) -> Result<Option<Vec<u8>>, NoiseError> {
|
||||
if self.handshake_state != HandshakeState::ReceivedMsg1 {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "received_msg1 state".to_string(),
|
||||
got: self.handshake_state.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut hs = self
|
||||
.noise_handshake
|
||||
.take()
|
||||
.expect("noise handshake must exist in ReceivedMsg1 state");
|
||||
|
||||
// Split msg3 into base XX part and optional negotiation
|
||||
let base_size = noise::XX_HANDSHAKE_MSG3_SIZE;
|
||||
let (base_msg3, extra) = if message.len() > base_size {
|
||||
(&message[..base_size], Some(&message[base_size..]))
|
||||
} else {
|
||||
(message, None)
|
||||
};
|
||||
|
||||
// Process XX msg3 (learns initiator identity + epoch)
|
||||
hs.read_xx_message_3(base_msg3)?;
|
||||
|
||||
// Decrypt negotiation payload from msg3 if present
|
||||
let received_negotiation = if let Some(encrypted) = extra {
|
||||
Some(hs.decrypt_payload(encrypted)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Learn initiator identity from msg3
|
||||
let remote_static = *hs
|
||||
.remote_static()
|
||||
.expect("remote static available after XX msg3");
|
||||
self.expected_identity = Some(PeerIdentity::from_pubkey_full(remote_static));
|
||||
|
||||
// Capture remote epoch from msg3
|
||||
self.remote_epoch = hs.remote_epoch();
|
||||
|
||||
// Handshake complete for responder
|
||||
let session = hs.into_session()?;
|
||||
self.noise_session = Some(session);
|
||||
self.handshake_state = HandshakeState::Complete;
|
||||
self.last_activity = current_time_ms;
|
||||
|
||||
Ok(received_negotiation)
|
||||
}
|
||||
|
||||
/// Take the completed Noise session.
|
||||
@@ -558,6 +655,7 @@ impl PeerConnection {
|
||||
pub fn is_timed_out(&self, current_time_ms: u64, timeout_ms: u64) -> bool {
|
||||
self.idle_time(current_time_ms) > timeout_ms
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl fmt::Debug for PeerConnection {
|
||||
@@ -650,35 +748,40 @@ mod tests {
|
||||
let responder_peer_id = PeerIdentity::from_pubkey_full(responder_identity.pubkey_full());
|
||||
|
||||
// Create connections
|
||||
let mut initiator_conn = PeerConnection::outbound(LinkId::new(1), responder_peer_id, 1000);
|
||||
let mut initiator_conn =
|
||||
PeerConnection::outbound(LinkId::new(1), responder_peer_id, 1000);
|
||||
let mut responder_conn = PeerConnection::inbound(LinkId::new(2), 1000);
|
||||
|
||||
// Initiator starts handshake
|
||||
let msg1 = initiator_conn
|
||||
.start_handshake(initiator_keypair, initiator_epoch, 1100)
|
||||
.unwrap();
|
||||
// Initiator starts XX handshake
|
||||
let msg1 = initiator_conn.start_handshake(initiator_keypair, initiator_epoch, 1100).unwrap();
|
||||
assert_eq!(initiator_conn.handshake_state(), HandshakeState::SentMsg1);
|
||||
|
||||
// Responder processes msg1 and sends msg2
|
||||
// Responder processes msg1 and sends msg2 (XX: does NOT complete yet)
|
||||
let msg2 = responder_conn
|
||||
.receive_handshake_init(responder_keypair, responder_epoch, &msg1, 1200)
|
||||
.receive_handshake_init(responder_keypair, responder_epoch, &msg1, None, 1200)
|
||||
.unwrap();
|
||||
assert_eq!(responder_conn.handshake_state(), HandshakeState::Complete);
|
||||
assert_eq!(responder_conn.handshake_state(), HandshakeState::ReceivedMsg1);
|
||||
// Responder does NOT know initiator's identity yet (XX property)
|
||||
assert!(responder_conn.expected_identity().is_none());
|
||||
|
||||
// Responder learned initiator's identity
|
||||
let discovered = responder_conn.expected_identity().unwrap();
|
||||
assert_eq!(discovered.pubkey(), initiator_identity.pubkey());
|
||||
|
||||
// Responder learned initiator's epoch
|
||||
assert_eq!(responder_conn.remote_epoch(), Some(initiator_epoch));
|
||||
|
||||
// Initiator completes handshake
|
||||
initiator_conn.complete_handshake(&msg2, 1300).unwrap();
|
||||
// Initiator processes msg2 and generates msg3
|
||||
let (msg3, _neg) = initiator_conn.complete_handshake(&msg2, None, 1300).unwrap();
|
||||
assert_eq!(initiator_conn.handshake_state(), HandshakeState::Complete);
|
||||
|
||||
// Initiator learned responder's epoch
|
||||
// Initiator learned responder's identity from msg2
|
||||
let discovered = initiator_conn.expected_identity().unwrap();
|
||||
assert_eq!(discovered.pubkey(), responder_identity.pubkey());
|
||||
assert_eq!(initiator_conn.remote_epoch(), Some(responder_epoch));
|
||||
|
||||
// Responder processes msg3 (completes handshake)
|
||||
let _neg = responder_conn.complete_handshake_msg3(&msg3, 1400).unwrap();
|
||||
assert_eq!(responder_conn.handshake_state(), HandshakeState::Complete);
|
||||
|
||||
// Responder learned initiator's identity from msg3
|
||||
let discovered = responder_conn.expected_identity().unwrap();
|
||||
assert_eq!(discovered.pubkey(), initiator_identity.pubkey());
|
||||
assert_eq!(responder_conn.remote_epoch(), Some(initiator_epoch));
|
||||
|
||||
// Both have sessions
|
||||
assert!(initiator_conn.has_session());
|
||||
assert!(responder_conn.has_session());
|
||||
@@ -723,18 +826,12 @@ mod tests {
|
||||
|
||||
// Outbound can't receive_handshake_init
|
||||
let mut outbound = PeerConnection::outbound(LinkId::new(1), identity, 1000);
|
||||
assert!(
|
||||
outbound
|
||||
.receive_handshake_init(keypair, make_epoch(), &[0u8; 106], 1100)
|
||||
.is_err()
|
||||
);
|
||||
assert!(outbound
|
||||
.receive_handshake_init(keypair, make_epoch(), &[0u8; 33], None, 1100)
|
||||
.is_err());
|
||||
|
||||
// Inbound can't start_handshake
|
||||
let mut inbound = PeerConnection::inbound(LinkId::new(2), 1000);
|
||||
assert!(
|
||||
inbound
|
||||
.start_handshake(keypair, make_epoch(), 1100)
|
||||
.is_err()
|
||||
);
|
||||
assert!(inbound.start_handshake(keypair, make_epoch(), 1100).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user