use std::net::SocketAddr;
const MAX_DIRECT_ADDRESSES: usize = 16;
const MAX_UNVERIFIED_ADDRESSES: usize = 16;
#[derive(Debug, Default)]
pub(crate) struct ExternalAddresses {
direct: Vec<SocketAddr>,
unverified: Vec<SocketAddr>,
relay: Option<SocketAddr>,
}
impl ExternalAddresses {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn pin_direct(&mut self, addr: SocketAddr) -> bool {
let addr = saorsa_transport::shared::normalize_socket_addr(addr);
if Some(addr) == self.relay || self.direct.contains(&addr) {
return false;
}
self.unverified.retain(|unverified| *unverified != addr);
if self.direct.len() >= MAX_DIRECT_ADDRESSES {
self.direct.remove(0);
}
self.direct.push(addr);
true
}
pub(crate) fn record_unverified(&mut self, addr: SocketAddr) -> bool {
let addr = saorsa_transport::shared::normalize_socket_addr(addr);
if Some(addr) == self.relay || self.direct.contains(&addr) {
return false;
}
if let Some(pos) = self
.unverified
.iter()
.position(|unverified| *unverified == addr)
{
let existing = self.unverified.remove(pos);
self.unverified.push(existing);
return false;
}
if self.unverified.len() >= MAX_UNVERIFIED_ADDRESSES {
self.unverified.remove(0);
}
self.unverified.push(addr);
true
}
pub(crate) fn set_relay(&mut self, addr: SocketAddr) {
let addr = saorsa_transport::shared::normalize_socket_addr(addr);
self.direct.retain(|direct| *direct != addr);
self.unverified.retain(|unverified| *unverified != addr);
self.relay = Some(addr);
}
pub(crate) fn clear_relay(&mut self) {
self.relay = None;
}
pub(crate) fn all_addresses(&self) -> Vec<SocketAddr> {
let mut out = Vec::with_capacity(self.direct.len() + self.unverified.len() + 1);
if let Some(relay) = self.relay {
out.push(relay);
}
for &addr in &self.direct {
if Some(addr) != self.relay {
out.push(addr);
}
}
for &addr in &self.unverified {
if Some(addr) != self.relay && !self.direct.contains(&addr) {
out.push(addr);
}
}
out
}
pub(crate) fn direct_addresses(&self) -> Vec<SocketAddr> {
self.direct.clone()
}
pub(crate) fn non_relay_addresses(&self) -> Vec<SocketAddr> {
let mut out = Vec::with_capacity(self.direct.len() + self.unverified.len());
out.extend(self.direct.iter().copied());
for &addr in &self.unverified {
if !self.direct.contains(&addr) {
out.push(addr);
}
}
out
}
pub(crate) fn relay_address(&self) -> Option<SocketAddr> {
self.relay
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
fn addr(last_octet: u8, port: u16) -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 0, 2, last_octet)), port)
}
#[test]
fn empty_returns_nothing() {
let ext = ExternalAddresses::new();
assert!(ext.all_addresses().is_empty());
assert!(ext.direct_addresses().is_empty());
assert!(ext.relay_address().is_none());
}
#[test]
fn pin_direct_dedup() {
let mut ext = ExternalAddresses::new();
let a = addr(1, 9000);
assert!(ext.pin_direct(a));
assert!(!ext.pin_direct(a));
assert!(!ext.pin_direct(a));
assert_eq!(ext.direct_addresses(), vec![a]);
assert_eq!(ext.all_addresses(), vec![a]);
}
#[test]
fn all_addresses_relay_first() {
let mut ext = ExternalAddresses::new();
let direct = addr(1, 9000);
let relay = addr(2, 9000);
ext.pin_direct(direct);
ext.set_relay(relay);
assert_eq!(ext.all_addresses(), vec![relay, direct]);
assert_eq!(ext.relay_address(), Some(relay));
}
#[test]
fn all_addresses_includes_unverified_after_relay() {
let mut ext = ExternalAddresses::new();
let relay = addr(1, 9000);
let unverified = addr(2, 9000);
ext.set_relay(relay);
assert!(ext.record_unverified(unverified));
assert_eq!(ext.all_addresses(), vec![relay, unverified]);
assert_eq!(ext.non_relay_addresses(), vec![unverified]);
}
#[test]
fn pin_direct_removes_matching_unverified() {
let mut ext = ExternalAddresses::new();
let direct = addr(1, 9000);
assert!(ext.record_unverified(direct));
assert!(ext.pin_direct(direct));
assert_eq!(ext.direct_addresses(), vec![direct]);
assert_eq!(ext.non_relay_addresses(), vec![direct]);
assert_eq!(ext.all_addresses(), vec![direct]);
}
#[test]
fn set_relay_removes_matching_unverified() {
let mut ext = ExternalAddresses::new();
let relay = addr(1, 9000);
assert!(ext.record_unverified(relay));
ext.set_relay(relay);
assert_eq!(ext.all_addresses(), vec![relay]);
assert!(ext.non_relay_addresses().is_empty());
}
#[test]
fn clear_relay_removes_from_all() {
let mut ext = ExternalAddresses::new();
let direct = addr(1, 9000);
let relay = addr(2, 9000);
ext.pin_direct(direct);
ext.set_relay(relay);
ext.clear_relay();
assert_eq!(ext.all_addresses(), vec![direct]);
assert!(ext.relay_address().is_none());
}
#[test]
fn relay_not_duplicated_when_also_pinned() {
let mut ext = ExternalAddresses::new();
let same = addr(1, 9000);
ext.pin_direct(same);
ext.set_relay(same);
assert_eq!(ext.all_addresses(), vec![same]);
assert!(ext.direct_addresses().is_empty());
}
#[test]
fn relay_address_is_not_pinned_as_direct() {
let mut ext = ExternalAddresses::new();
let same = addr(1, 9000);
ext.set_relay(same);
assert!(!ext.pin_direct(same));
assert!(ext.direct_addresses().is_empty());
assert_eq!(ext.all_addresses(), vec![same]);
}
#[test]
fn relay_address_is_normalized_before_direct_dedup() {
let mut ext = ExternalAddresses::new();
let plain = addr(1, 9000);
let mapped = "[::ffff:192.0.2.1]:9000".parse().unwrap();
ext.set_relay(mapped);
assert_eq!(ext.relay_address(), Some(plain));
assert!(!ext.pin_direct(plain));
assert!(ext.direct_addresses().is_empty());
assert_eq!(ext.all_addresses(), vec![plain]);
}
#[test]
fn multiple_direct_addresses_preserved() {
let mut ext = ExternalAddresses::new();
let a = addr(1, 9000);
let b = addr(2, 9000);
ext.pin_direct(a);
ext.pin_direct(b);
assert_eq!(ext.direct_addresses(), vec![a, b]);
assert_eq!(ext.all_addresses(), vec![a, b]);
}
#[test]
fn direct_addresses_excludes_relay() {
let mut ext = ExternalAddresses::new();
let direct = addr(1, 9000);
let relay = addr(2, 9000);
ext.pin_direct(direct);
ext.set_relay(relay);
assert_eq!(ext.direct_addresses(), vec![direct]);
}
#[test]
fn pin_direct_evicts_oldest_at_capacity() {
let mut ext = ExternalAddresses::new();
for i in 0..MAX_DIRECT_ADDRESSES as u8 {
ext.pin_direct(addr(i, 9000));
}
assert_eq!(ext.direct_addresses().len(), MAX_DIRECT_ADDRESSES);
let new = addr(MAX_DIRECT_ADDRESSES as u8, 9000);
ext.pin_direct(new);
assert_eq!(ext.direct_addresses().len(), MAX_DIRECT_ADDRESSES);
assert!(!ext.direct_addresses().contains(&addr(0, 9000)));
assert_eq!(*ext.direct_addresses().last().unwrap(), new);
}
}