Add epoch-based peer restart detection to Noise IK handshake

Each node generates a random 8-byte startup epoch, encrypted inside
both Noise IK handshake messages (msg1 and msg2). When a peer's msg1
arrives with a different epoch than the stored value, the node tears
down the stale session and processes the msg1 as a new connection,
enabling near-instant restart detection instead of the 30-second
dead timeout.

Wire format impact:
- msg1: 82 -> 106 bytes (added 24-byte encrypted epoch after ss DH)
- msg2: 33 -> 57 bytes (added 24-byte encrypted epoch after se DH)
- Wire msg1: 90 -> 114 bytes, wire msg2: 45 -> 69 bytes
This commit is contained in:
Johnathan Corgan
2026-02-22 20:50:50 +00:00
parent 1adfd9e90f
commit f920526ece
15 changed files with 338 additions and 87 deletions
+101 -32
View File
@@ -38,47 +38,64 @@ impl Node {
// Check for existing connection from this address.
//
// If we already have an *inbound* link from this address, this is a
// duplicate msg1 (our msg2 was probably lost). Resend msg2 if available.
// If we already have an *inbound* link from this address, this could be:
// 1. A duplicate msg1 (our msg2 was lost) — resend msg2
// 2. A restarted peer (different epoch) — tear down and reprocess
//
// If we have an *outbound* link to this address (we initiated to them
// AND they initiated to us), this is a cross-connection — allow it.
//
// Epoch-based restart detection: if the sender already has an inbound
// link AND is an active peer in self.peers, fall through to decrypt
// the msg1 and check the epoch. Otherwise, treat as duplicate.
let addr_key = (packet.transport_id, packet.remote_addr.clone());
let mut possible_restart = false;
if let Some(&existing_link_id) = self.addr_to_link.get(&addr_key)
&& let Some(link) = self.links.get(&existing_link_id)
{
if link.direction() == LinkDirection::Inbound {
// Duplicate msg1 — try to resend stored msg2
let msg2_bytes = self.find_stored_msg2(existing_link_id);
if let Some(msg2) = msg2_bytes {
if let Some(transport) = self.transports.get(&packet.transport_id) {
match transport.send(&packet.remote_addr, &msg2).await {
Ok(_) => debug!(
remote_addr = %packet.remote_addr,
"Resent msg2 for duplicate msg1"
),
Err(e) => debug!(
remote_addr = %packet.remote_addr,
error = %e,
"Failed to resend msg2"
),
}
}
// Check if this link belongs to an already-promoted active peer
let is_active_peer = self.peers.values()
.any(|p| p.link_id() == existing_link_id);
if is_active_peer {
// Possible restart — fall through to decrypt and check epoch
possible_restart = true;
} else {
debug!(
remote_addr = %packet.remote_addr,
"Duplicate msg1 but no stored msg2 to resend"
);
// Genuinely pending handshake — resend msg2
let msg2_bytes = self.find_stored_msg2(existing_link_id);
if let Some(msg2) = msg2_bytes {
if let Some(transport) = self.transports.get(&packet.transport_id) {
match transport.send(&packet.remote_addr, &msg2).await {
Ok(_) => debug!(
remote_addr = %packet.remote_addr,
"Resent msg2 for duplicate msg1"
),
Err(e) => debug!(
remote_addr = %packet.remote_addr,
error = %e,
"Failed to resend msg2"
),
}
}
} else {
debug!(
remote_addr = %packet.remote_addr,
"Duplicate msg1 but no stored msg2 to resend"
);
}
self.msg1_rate_limiter.complete_handshake();
return;
}
self.msg1_rate_limiter.complete_handshake();
return;
}
// Outbound link to this address — cross-connection, allow msg1
debug!(
transport_id = %packet.transport_id,
remote_addr = %packet.remote_addr,
existing_link_id = %existing_link_id,
"Cross-connection detected: have outbound, received inbound msg1"
} else {
// Outbound link to this address — cross-connection, allow msg1
debug!(
transport_id = %packet.transport_id,
remote_addr = %packet.remote_addr,
existing_link_id = %existing_link_id,
"Cross-connection detected: have outbound, received inbound msg1"
);
}
}
// === CRYPTO COST PAID HERE ===
@@ -92,7 +109,7 @@ impl Node {
let our_keypair = self.identity.keypair();
let noise_msg1 = &packet.data[header.noise_msg1_offset..];
let msg2_response = match conn.receive_handshake_init(our_keypair, noise_msg1, packet.timestamp_ms) {
let msg2_response = match conn.receive_handshake_init(our_keypair, self.startup_epoch, noise_msg1, packet.timestamp_ms) {
Ok(m) => m,
Err(e) => {
self.msg1_rate_limiter.complete_handshake();
@@ -114,6 +131,55 @@ impl Node {
}
};
let peer_node_addr = *peer_identity.node_addr();
// Epoch-based restart detection and duplicate msg1 handling.
//
// If we fell through from the addr_to_link check above with
// possible_restart=true, we now have the decrypted epoch from msg1.
// Compare it against the stored epoch for this peer.
if possible_restart
&& let Some(existing_peer) = self.peers.get(&peer_node_addr)
{
let new_epoch = conn.remote_epoch();
let existing_epoch = existing_peer.remote_epoch();
match (existing_epoch, new_epoch) {
(Some(existing), Some(new)) if existing != new => {
// Epoch mismatch — peer restarted. Tear down stale session.
info!(
peer = %self.peer_display_name(&peer_node_addr),
"Peer restart detected (epoch mismatch), removing stale session"
);
self.remove_active_peer(&peer_node_addr);
// Fall through to process as new connection
}
_ => {
// Same epoch (or no epoch stored) — duplicate msg1 from
// same session. Resend stored msg2.
if let Some(msg2) = existing_peer.handshake_msg2().map(|m| m.to_vec())
&& let Some(transport) = self.transports.get(&packet.transport_id)
{
match transport.send(&packet.remote_addr, &msg2).await {
Ok(_) => debug!(
peer = %self.peer_display_name(&peer_node_addr),
"Resent msg2 for duplicate msg1 (same epoch)"
),
Err(e) => debug!(
peer = %self.peer_display_name(&peer_node_addr),
error = %e,
"Failed to resend msg2"
),
}
}
self.msg1_rate_limiter.complete_handshake();
return;
}
}
}
// If possible_restart was true but peer is no longer in self.peers
// (removed by another path), fall through to process as new connection.
// Note: we don't early-return if peer is already in self.peers here.
// promote_connection handles cross-connection resolution via tie-breaker.
@@ -560,6 +626,7 @@ impl Node {
}
})?.clone();
let link_stats = connection.link_stats().clone();
let remote_epoch = connection.remote_epoch();
let peer_node_addr = *verified_identity.node_addr();
let is_outbound = connection.is_outbound();
@@ -601,6 +668,7 @@ impl Node {
link_stats,
is_outbound,
&self.config.node.mmp,
remote_epoch,
);
new_peer.set_tree_announce_min_interval_ms(self.config.node.tree.announce_min_interval_ms);
@@ -683,6 +751,7 @@ impl Node {
link_stats,
is_outbound,
&self.config.node.mmp,
remote_epoch,
);
new_peer.set_tree_announce_min_interval_ms(self.config.node.tree.announce_min_interval_ms);
+2
View File
@@ -337,6 +337,7 @@ impl Node {
// Create responder handshake and process msg1
let our_keypair = self.identity.keypair();
let mut handshake = HandshakeState::new_responder(our_keypair);
handshake.set_local_epoch(self.startup_epoch);
if let Err(e) = handshake.read_message_1(&setup.handshake_payload) {
debug!(error = %e, "Failed to process Noise IK msg1 in SessionSetup");
@@ -724,6 +725,7 @@ impl Node {
// Create Noise IK initiator handshake
let our_keypair = self.identity.keypair();
let mut handshake = HandshakeState::new_initiator(our_keypair, dest_pubkey);
handshake.set_local_epoch(self.startup_epoch);
let msg1 = handshake.write_message_1().map_err(|e| NodeError::SendFailed {
node_addr: dest_addr,
reason: format!("Noise msg1 generation failed: {}", e),
+1 -1
View File
@@ -147,7 +147,7 @@ impl Node {
// Start the Noise handshake and get message 1
let our_keypair = self.identity.keypair();
let noise_msg1 = match connection.start_handshake(our_keypair, current_time_ms) {
let noise_msg1 = match connection.start_handshake(our_keypair, self.startup_epoch, current_time_ms) {
Ok(msg) => msg,
Err(e) => {
warn!(
+14
View File
@@ -33,6 +33,7 @@ use crate::upper::icmp_rate_limit::IcmpRateLimiter;
use crate::upper::tun::{TunError, TunOutboundRx, TunState, TunTx};
use self::wire::{build_encrypted, build_established_header, prepend_inner_header, FLAG_SP};
use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity};
use rand::RngCore;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::thread::JoinHandle;
@@ -198,6 +199,10 @@ pub struct Node {
/// This node's cryptographic identity.
identity: Identity,
/// Random epoch generated at startup for peer restart detection.
/// Exchanged inside Noise handshake messages so peers can detect restarts.
startup_epoch: [u8; 8],
// === Configuration ===
/// Loaded configuration.
config: Config,
@@ -342,6 +347,9 @@ impl Node {
let node_addr = *identity.node_addr();
let is_leaf_only = config.is_leaf_only();
let mut startup_epoch = [0u8; 8];
rand::thread_rng().fill_bytes(&mut startup_epoch);
let mut bloom_state = if is_leaf_only {
BloomState::leaf_only(node_addr)
} else {
@@ -379,6 +387,7 @@ impl Node {
Ok(Self {
identity,
startup_epoch,
config,
state: NodeState::Created,
is_leaf_only,
@@ -427,6 +436,10 @@ impl Node {
/// Create a node with a specific identity.
pub fn with_identity(identity: Identity, config: Config) -> Self {
let node_addr = *identity.node_addr();
let mut startup_epoch = [0u8; 8];
rand::thread_rng().fill_bytes(&mut startup_epoch);
let tun_state = if config.tun.enabled {
TunState::Configured
} else {
@@ -460,6 +473,7 @@ impl Node {
Self {
identity,
startup_epoch,
config,
state: NodeState::Created,
is_leaf_only: false,
+9 -9
View File
@@ -65,7 +65,7 @@ async fn test_two_node_handshake_udp() {
// Start handshake (generates Noise IK msg1)
let our_keypair_a = node_a.identity.keypair();
let noise_msg1 = conn_a.start_handshake(our_keypair_a, 1000).unwrap();
let noise_msg1 = conn_a.start_handshake(our_keypair_a, node_a.startup_epoch, 1000).unwrap();
conn_a.set_our_index(our_index_a);
conn_a.set_transport_id(transport_id_a);
conn_a.set_source_addr(remote_addr_b.clone());
@@ -303,7 +303,7 @@ async fn test_run_rx_loop_handshake() {
let our_index_a = node_a.index_allocator.allocate().unwrap();
let our_keypair_a = node_a.identity.keypair();
let noise_msg1 = conn_a.start_handshake(our_keypair_a, 1000).unwrap();
let noise_msg1 = conn_a.start_handshake(our_keypair_a, node_a.startup_epoch, 1000).unwrap();
conn_a.set_our_index(our_index_a);
conn_a.set_transport_id(transport_id_a);
conn_a.set_source_addr(remote_addr_b.clone());
@@ -488,7 +488,7 @@ async fn test_cross_connection_both_initiate() {
let mut conn_a = PeerConnection::outbound(link_id_a_out, peer_b_identity, 1000);
let our_index_a = node_a.index_allocator.allocate().unwrap();
let our_keypair_a = node_a.identity.keypair();
let noise_msg1_a = conn_a.start_handshake(our_keypair_a, 1000).unwrap();
let noise_msg1_a = conn_a.start_handshake(our_keypair_a, node_a.startup_epoch, 1000).unwrap();
conn_a.set_our_index(our_index_a);
conn_a.set_transport_id(transport_id_a);
conn_a.set_source_addr(remote_addr_b.clone());
@@ -509,7 +509,7 @@ async fn test_cross_connection_both_initiate() {
let mut conn_b = PeerConnection::outbound(link_id_b_out, peer_a_identity, 1000);
let our_index_b = node_b.index_allocator.allocate().unwrap();
let our_keypair_b = node_b.identity.keypair();
let noise_msg1_b = conn_b.start_handshake(our_keypair_b, 1000).unwrap();
let noise_msg1_b = conn_b.start_handshake(our_keypair_b, node_b.startup_epoch, 1000).unwrap();
conn_b.set_our_index(our_index_b);
conn_b.set_transport_id(transport_id_b);
conn_b.set_source_addr(remote_addr_a.clone());
@@ -611,7 +611,7 @@ async fn test_stale_connection_cleanup() {
// Allocate session index and set transport info
let our_index = node.index_allocator.allocate().unwrap();
let our_keypair = node.identity.keypair();
let _noise_msg1 = conn.start_handshake(our_keypair, past_time_ms).unwrap();
let _noise_msg1 = conn.start_handshake(our_keypair, node.startup_epoch, past_time_ms).unwrap();
conn.set_our_index(our_index);
conn.set_transport_id(transport_id);
conn.set_source_addr(remote_addr.clone());
@@ -665,7 +665,7 @@ async fn test_failed_connection_cleanup() {
let our_index = node.index_allocator.allocate().unwrap();
let our_keypair = node.identity.keypair();
let _noise_msg1 = conn.start_handshake(our_keypair, now_ms).unwrap();
let _noise_msg1 = conn.start_handshake(our_keypair, node.startup_epoch, now_ms).unwrap();
conn.set_our_index(our_index);
conn.set_transport_id(transport_id);
conn.set_source_addr(remote_addr.clone());
@@ -710,7 +710,7 @@ async fn test_msg1_stored_for_resend() {
let our_index = node.index_allocator.allocate().unwrap();
let our_keypair = node.identity.keypair();
let noise_msg1 = conn.start_handshake(our_keypair, now_ms).unwrap();
let noise_msg1 = conn.start_handshake(our_keypair, node.startup_epoch, now_ms).unwrap();
conn.set_our_index(our_index);
conn.set_transport_id(transport_id);
conn.set_source_addr(remote_addr.clone());
@@ -741,7 +741,7 @@ async fn test_resend_scheduling() {
let our_index = node.index_allocator.allocate().unwrap();
let our_keypair = node.identity.keypair();
let noise_msg1 = conn.start_handshake(our_keypair, now_ms).unwrap();
let noise_msg1 = conn.start_handshake(our_keypair, node.startup_epoch, now_ms).unwrap();
conn.set_our_index(our_index);
conn.set_transport_id(transport_id);
conn.set_source_addr(remote_addr.clone());
@@ -827,7 +827,7 @@ async fn test_duplicate_msg2_dropped() {
let sender_idx = SessionIndex::new(99);
// Build a fake msg2 packet
let fake_noise_msg2 = vec![0u8; 33]; // Noise IK msg2 is 33 bytes
let fake_noise_msg2 = vec![0u8; 57]; // Noise IK msg2 is 57 bytes (33 ephem + 24 encrypted epoch)
let wire_msg2 = build_msg2(sender_idx, receiver_idx, &fake_noise_msg2);
let packet = ReceivedPacket {
+4 -2
View File
@@ -50,13 +50,15 @@ pub(super) fn make_completed_connection(
// Run initiator side of handshake
let our_keypair = node.identity.keypair();
let msg1 = conn.start_handshake(our_keypair, current_time_ms).unwrap();
let msg1 = conn.start_handshake(our_keypair, node.startup_epoch, current_time_ms).unwrap();
// Run responder side to generate msg2
let mut resp_conn = PeerConnection::inbound(LinkId::new(999), current_time_ms);
let peer_keypair = peer_identity_full.keypair();
let mut resp_epoch = [0u8; 8];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut resp_epoch);
let msg2 = resp_conn
.receive_handshake_init(peer_keypair, &msg1, current_time_ms)
.receive_handshake_init(peer_keypair, resp_epoch, &msg1, current_time_ms)
.unwrap();
// Complete initiator handshake
+8
View File
@@ -1160,6 +1160,14 @@ fn make_noise_session(
);
let mut responder = HandshakeState::new_responder(remote_identity.keypair());
// Set epochs for both sides (required for handshake message encryption)
let mut init_epoch = [0u8; 8];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut init_epoch);
initiator.set_local_epoch(init_epoch);
let mut resp_epoch = [0u8; 8];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut resp_epoch);
responder.set_local_epoch(resp_epoch);
let msg1 = initiator.write_message_1().unwrap();
responder.read_message_1(&msg1).unwrap();
let msg2 = responder.write_message_2().unwrap();
+1 -1
View File
@@ -64,7 +64,7 @@ pub(super) async fn initiate_handshake(nodes: &mut [TestNode], i: usize, j: usiz
let our_index = initiator.node.index_allocator.allocate().unwrap();
let our_keypair = initiator.node.identity().keypair();
let noise_msg1 = conn.start_handshake(our_keypair, 1000).unwrap();
let noise_msg1 = conn.start_handshake(our_keypair, initiator.node.startup_epoch, 1000).unwrap();
conn.set_our_index(our_index);
conn.set_transport_id(transport_id);
conn.set_source_addr(responder_addr.clone());
+5 -3
View File
@@ -450,7 +450,7 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() {
PeerConnection::outbound(pending_link_id, peer_b_identity, pending_time_ms);
let our_keypair = node.identity.keypair();
let _msg1 = pending_conn.start_handshake(our_keypair, pending_time_ms).unwrap();
let _msg1 = pending_conn.start_handshake(our_keypair, node.startup_epoch, pending_time_ms).unwrap();
let pending_index = node.index_allocator.allocate().unwrap();
pending_conn.set_our_index(pending_index);
@@ -491,14 +491,16 @@ fn test_promote_cleans_up_pending_outbound_to_same_peer() {
let our_keypair = node.identity.keypair();
let msg1 = completing_conn
.start_handshake(our_keypair, completing_time_ms)
.start_handshake(our_keypair, node.startup_epoch, completing_time_ms)
.unwrap();
// B responds
let mut resp_conn = PeerConnection::inbound(LinkId::new(999), completing_time_ms);
let peer_keypair = peer_b_full.keypair();
let mut resp_epoch = [0u8; 8];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut resp_epoch);
let msg2 = resp_conn
.receive_handshake_init(peer_keypair, &msg1, completing_time_ms)
.receive_handshake_init(peer_keypair, resp_epoch, &msg1, completing_time_ms)
.unwrap();
completing_conn
+19 -19
View File
@@ -11,11 +11,11 @@
//!
//! ## Packet Types
//!
//! | Phase | Type | Size | Description |
//! |-------|-----------------|-----------|--------------------------------|
//! | 0x0 | Encrypted frame | 32+ bytes | Post-handshake encrypted data |
//! | 0x1 | Noise IK msg1 | 90 bytes | Handshake initiation |
//! | 0x2 | Noise IK msg2 | 45 bytes | Handshake response |
//! | Phase | Type | Size | Description |
//! |-------|-----------------|------------|--------------------------------|
//! | 0x0 | Encrypted frame | 32+ bytes | Post-handshake encrypted data |
//! | 0x1 | Noise IK msg1 | 114 bytes | Handshake initiation |
//! | 0x2 | Noise IK msg2 | 69 bytes | Handshake response |
use crate::utils::index::SessionIndex;
use crate::noise::{HANDSHAKE_MSG1_SIZE, HANDSHAKE_MSG2_SIZE, TAG_SIZE};
@@ -43,10 +43,10 @@ pub const COMMON_PREFIX_SIZE: usize = 4;
pub const ESTABLISHED_HEADER_SIZE: usize = 16;
/// Size of Noise IK message 1 wire packet: prefix + sender_idx + noise_msg1.
pub const MSG1_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + HANDSHAKE_MSG1_SIZE; // 90 bytes
pub const MSG1_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + HANDSHAKE_MSG1_SIZE; // 114 bytes
/// Size of Noise IK message 2 wire packet: prefix + sender_idx + receiver_idx + noise_msg2.
pub const MSG2_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + HANDSHAKE_MSG2_SIZE; // 45 bytes
pub const MSG2_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + HANDSHAKE_MSG2_SIZE; // 69 bytes
/// Minimum size for encrypted frame: header + tag (no plaintext).
pub const ENCRYPTED_MIN_SIZE: usize = ESTABLISHED_HEADER_SIZE + TAG_SIZE; // 32 bytes
@@ -198,9 +198,9 @@ impl EncryptedHeader {
/// Parsed Noise IK message 1 header (phase 0x1).
///
/// Wire format (90 bytes):
/// Wire format (114 bytes):
/// ```text
/// [0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:82]
/// [0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:106]
/// ```
#[derive(Clone, Debug)]
pub struct Msg1Header {
@@ -252,9 +252,9 @@ impl Msg1Header {
/// Parsed Noise IK message 2 header (phase 0x2).
///
/// Wire format (45 bytes):
/// Wire format (69 bytes):
/// ```text
/// [0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:33]
/// [0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:57]
/// ```
#[derive(Clone, Debug)]
pub struct Msg2Header {
@@ -310,7 +310,7 @@ impl Msg2Header {
/// Build a wire-format msg1 packet.
///
/// Format: `[0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:82]`
/// Format: `[0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:106]`
pub fn build_msg1(sender_idx: SessionIndex, noise_msg1: &[u8]) -> Vec<u8> {
debug_assert_eq!(noise_msg1.len(), HANDSHAKE_MSG1_SIZE);
@@ -327,7 +327,7 @@ pub fn build_msg1(sender_idx: SessionIndex, noise_msg1: &[u8]) -> Vec<u8> {
/// Build a wire-format msg2 packet.
///
/// Format: `[0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:33]`
/// Format: `[0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:57]`
pub fn build_msg2(sender_idx: SessionIndex, receiver_idx: SessionIndex, noise_msg2: &[u8]) -> Vec<u8> {
debug_assert_eq!(noise_msg2.len(), HANDSHAKE_MSG2_SIZE);
@@ -542,8 +542,8 @@ mod tests {
#[test]
fn test_wire_sizes() {
assert_eq!(MSG1_WIRE_SIZE, 90); // 4 + 4 + 82
assert_eq!(MSG2_WIRE_SIZE, 45); // 4 + 4 + 4 + 33
assert_eq!(MSG1_WIRE_SIZE, 114); // 4 + 4 + 106
assert_eq!(MSG2_WIRE_SIZE, 69); // 4 + 4 + 4 + 57
assert_eq!(ENCRYPTED_MIN_SIZE, 32); // 16 + 16
assert_eq!(COMMON_PREFIX_SIZE, 4);
assert_eq!(ESTABLISHED_HEADER_SIZE, 16);
@@ -610,8 +610,8 @@ mod tests {
fn test_payload_len_in_msg1() {
let packet = build_msg1(SessionIndex::new(1), &[0u8; HANDSHAKE_MSG1_SIZE]);
let prefix = CommonPrefix::parse(&packet).unwrap();
// payload_len = sender_idx(4) + noise_msg1(82) = 86
assert_eq!(prefix.payload_len, 86);
// payload_len = sender_idx(4) + noise_msg1(106) = 110
assert_eq!(prefix.payload_len, 110);
}
#[test]
@@ -622,7 +622,7 @@ mod tests {
&[0u8; HANDSHAKE_MSG2_SIZE],
);
let prefix = CommonPrefix::parse(&packet).unwrap();
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg2(33) = 41
assert_eq!(prefix.payload_len, 41);
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg2(57) = 65
assert_eq!(prefix.payload_len, 65);
}
}