mod active;
mod connection;
pub use active::{ActivePeer, ConnectivityState};
pub use connection::{HandshakeState, PeerConnection};
use crate::NodeAddr;
use crate::transport::LinkId;
use std::fmt;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PeerError {
#[error("peer not authenticated")]
NotAuthenticated,
#[error("peer not found: {0:?}")]
NotFound(NodeAddr),
#[error("connection not found: {0}")]
ConnectionNotFound(LinkId),
#[error("peer already exists: {0:?}")]
AlreadyExists(NodeAddr),
#[error("handshake failed: {0}")]
HandshakeFailed(String),
#[error("handshake timeout")]
HandshakeTimeout,
#[error("identity mismatch: expected {expected:?}, got {actual:?}")]
IdentityMismatch {
expected: NodeAddr,
actual: NodeAddr,
},
#[error("peer disconnected")]
Disconnected,
#[error("max connections exceeded: {max}")]
MaxConnectionsExceeded { max: usize },
#[error("max peers exceeded: {max}")]
MaxPeersExceeded { max: usize },
}
#[derive(Debug, Clone, Copy)]
pub enum PromotionResult {
Promoted(NodeAddr),
CrossConnectionLost {
winner_link_id: LinkId,
},
CrossConnectionWon {
loser_link_id: LinkId,
node_addr: NodeAddr,
},
}
impl PromotionResult {
pub fn node_addr(&self) -> Option<NodeAddr> {
match self {
PromotionResult::Promoted(node_addr) => Some(*node_addr),
PromotionResult::CrossConnectionWon { node_addr, .. } => Some(*node_addr),
PromotionResult::CrossConnectionLost { .. } => None,
}
}
pub fn should_close_this_connection(&self) -> bool {
matches!(self, PromotionResult::CrossConnectionLost { .. })
}
pub fn link_to_close(&self) -> Option<LinkId> {
match self {
PromotionResult::CrossConnectionLost { .. } => None, PromotionResult::CrossConnectionWon { loser_link_id, .. } => Some(*loser_link_id),
PromotionResult::Promoted(_) => None,
}
}
}
pub fn cross_connection_winner(
our_node_addr: &NodeAddr,
their_node_addr: &NodeAddr,
this_is_outbound: bool,
) -> bool {
let we_are_smaller = our_node_addr < their_node_addr;
if we_are_smaller {
this_is_outbound
} else {
!this_is_outbound
}
}
#[derive(Debug)]
pub enum PeerSlot {
Connecting(Box<PeerConnection>),
Active(Box<ActivePeer>),
}
impl PeerSlot {
pub fn outbound(conn: PeerConnection) -> Self {
PeerSlot::Connecting(Box::new(conn))
}
pub fn inbound(conn: PeerConnection) -> Self {
PeerSlot::Connecting(Box::new(conn))
}
pub fn active(peer: ActivePeer) -> Self {
PeerSlot::Active(Box::new(peer))
}
pub fn is_connecting(&self) -> bool {
matches!(self, PeerSlot::Connecting(_))
}
pub fn is_active(&self) -> bool {
matches!(self, PeerSlot::Active(_))
}
pub fn link_id(&self) -> LinkId {
match self {
PeerSlot::Connecting(conn) => conn.link_id(),
PeerSlot::Active(peer) => peer.link_id(),
}
}
pub fn as_connection(&self) -> Option<&PeerConnection> {
match self {
PeerSlot::Connecting(conn) => Some(conn),
PeerSlot::Active(_) => None,
}
}
pub fn as_connection_mut(&mut self) -> Option<&mut PeerConnection> {
match self {
PeerSlot::Connecting(conn) => Some(conn),
PeerSlot::Active(_) => None,
}
}
pub fn as_active(&self) -> Option<&ActivePeer> {
match self {
PeerSlot::Active(peer) => Some(peer),
PeerSlot::Connecting(_) => None,
}
}
pub fn as_active_mut(&mut self) -> Option<&mut ActivePeer> {
match self {
PeerSlot::Active(peer) => Some(peer),
PeerSlot::Connecting(_) => None,
}
}
pub fn node_addr(&self) -> Option<&NodeAddr> {
match self {
PeerSlot::Connecting(conn) => conn.expected_identity().map(|id| id.node_addr()),
PeerSlot::Active(peer) => Some(peer.node_addr()),
}
}
}
impl fmt::Display for PeerSlot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PeerSlot::Connecting(conn) => {
write!(
f,
"connecting(link={}, state={})",
conn.link_id(),
conn.handshake_state()
)
}
PeerSlot::Active(peer) => {
write!(
f,
"active(node={:?}, link={})",
peer.node_addr(),
peer.link_id()
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::LinkId;
use crate::{Identity, PeerIdentity};
fn make_node_addr(val: u8) -> NodeAddr {
let mut bytes = [0u8; 16];
bytes[0] = val;
NodeAddr::from_bytes(bytes)
}
fn make_peer_identity() -> PeerIdentity {
let identity = Identity::generate();
PeerIdentity::from_pubkey(identity.pubkey())
}
#[test]
fn test_cross_connection_smaller_node_wins_outbound() {
let node_a = make_node_addr(1); let node_b = make_node_addr(2);
assert!(cross_connection_winner(&node_a, &node_b, true)); assert!(!cross_connection_winner(&node_a, &node_b, false));
assert!(!cross_connection_winner(&node_b, &node_a, true)); assert!(cross_connection_winner(&node_b, &node_a, false)); }
#[test]
fn test_cross_connection_symmetric() {
let node_a = make_node_addr(1);
let node_b = make_node_addr(2);
let a_outbound_wins = cross_connection_winner(&node_a, &node_b, true);
let b_inbound_wins = cross_connection_winner(&node_b, &node_a, false);
assert_eq!(a_outbound_wins, b_inbound_wins);
let a_inbound_wins = cross_connection_winner(&node_a, &node_b, false);
let b_outbound_wins = cross_connection_winner(&node_b, &node_a, true);
assert_eq!(a_inbound_wins, b_outbound_wins);
assert!(a_outbound_wins != a_inbound_wins);
}
#[test]
fn test_peer_slot_connecting() {
let identity = make_peer_identity();
let conn = PeerConnection::outbound(LinkId::new(1), identity, 1000);
let slot = PeerSlot::Connecting(Box::new(conn));
assert!(slot.is_connecting());
assert!(!slot.is_active());
assert!(slot.as_connection().is_some());
assert!(slot.as_active().is_none());
assert_eq!(slot.link_id(), LinkId::new(1));
}
#[test]
fn test_peer_slot_active() {
let identity = make_peer_identity();
let peer = ActivePeer::new(identity, LinkId::new(2), 2000);
let slot = PeerSlot::Active(Box::new(peer));
assert!(!slot.is_connecting());
assert!(slot.is_active());
assert!(slot.as_connection().is_none());
assert!(slot.as_active().is_some());
assert_eq!(slot.link_id(), LinkId::new(2));
}
#[test]
fn test_promotion_result_promoted() {
let identity = make_peer_identity();
let node_addr = *identity.node_addr();
let result = PromotionResult::Promoted(node_addr);
assert!(result.node_addr().is_some());
assert_eq!(result.node_addr(), Some(node_addr));
assert!(!result.should_close_this_connection());
assert!(result.link_to_close().is_none());
}
#[test]
fn test_promotion_result_cross_lost() {
let result = PromotionResult::CrossConnectionLost {
winner_link_id: LinkId::new(1),
};
assert!(result.node_addr().is_none());
assert!(result.should_close_this_connection());
assert!(result.link_to_close().is_none()); }
#[test]
fn test_promotion_result_cross_won() {
let identity = make_peer_identity();
let node_addr = *identity.node_addr();
let result = PromotionResult::CrossConnectionWon {
loser_link_id: LinkId::new(1),
node_addr,
};
assert!(result.node_addr().is_some());
assert_eq!(result.node_addr(), Some(node_addr));
assert!(!result.should_close_this_connection());
assert_eq!(result.link_to_close(), Some(LinkId::new(1)));
}
#[test]
fn test_peer_slot_node_addr() {
let identity = make_peer_identity();
let expected_node_addr = *identity.node_addr();
let conn = PeerConnection::outbound(LinkId::new(1), identity, 1000);
let slot = PeerSlot::Connecting(Box::new(conn));
assert_eq!(slot.node_addr(), Some(&expected_node_addr));
let conn_inbound = PeerConnection::inbound(LinkId::new(2), 2000);
let slot_inbound = PeerSlot::Connecting(Box::new(conn_inbound));
assert!(slot_inbound.node_addr().is_none());
let identity2 = make_peer_identity();
let active_node_addr = *identity2.node_addr();
let peer = ActivePeer::new(identity2, LinkId::new(3), 3000);
let slot_active = PeerSlot::Active(Box::new(peer));
assert_eq!(slot_active.node_addr(), Some(&active_node_addr));
}
}