use crate::config::{GossipConfig, TransportConfig};
use crate::discovery::Discovery;
use crate::error::{Error, Result};
use crate::types::{Hash, Record};
use serde::{Deserialize, Serialize};
use smol::channel::{bounded, Sender};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
static REQUEST_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
#[cfg(feature = "coap")]
use crate::coap::CoapServer;
#[cfg(feature = "quic")]
use crate::quic::QuicServer;
#[derive(Debug, Clone)]
pub struct PeerInfo {
pub addr: SocketAddr,
pub last_seen: Instant,
pub latest_seq: u32,
pub quality: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Message {
Ping { node_id: String },
Pong { node_id: String, latest_seq: u32 },
GossipRequest { from_seq: u32, limit: u32 },
GossipResponse { records: Vec<Record> },
NewRecord { hash: Hash },
GetRecord { hash: Hash },
RecordData { record: Record },
RemoteCall {
request_id: String,
from: String,
method: String,
payload: Vec<u8>,
},
RemoteCallResponse {
request_id: String,
success: bool,
data: Vec<u8>,
},
MeshRelay {
message_id: String,
origin: String,
ttl: u8,
inner: Box<Message>,
},
}
#[derive(Debug)]
struct PendingRpc {
response_tx: Sender<RpcResponse>,
sent_at: Instant,
method: String,
}
#[derive(Debug, Clone)]
pub struct RpcResponse {
pub success: bool,
pub data: Vec<u8>,
}
pub const DEFAULT_MESH_TTL: u8 = 5;
const MAX_SEEN_MESSAGES: usize = 10000;
#[derive(Debug, Default)]
pub struct MeshManager {
seen_messages: std::collections::HashSet<String>,
seen_order: std::collections::VecDeque<String>,
pub stats: MeshStats,
}
#[derive(Debug, Clone, Default)]
pub struct MeshStats {
pub messages_relayed: u64,
pub messages_dropped_ttl: u64,
pub duplicates_ignored: u64,
pub messages_originated: u64,
}
impl MeshManager {
pub fn new() -> Self {
Self::default()
}
pub fn is_seen(&self, message_id: &str) -> bool {
self.seen_messages.contains(message_id)
}
pub fn mark_seen(&mut self, message_id: String) {
if self.seen_messages.insert(message_id.clone()) {
self.seen_order.push_back(message_id);
while self.seen_messages.len() > MAX_SEEN_MESSAGES {
if let Some(old_id) = self.seen_order.pop_front() {
self.seen_messages.remove(&old_id);
}
}
}
}
pub fn process_relay(&mut self, message_id: &str, ttl: u8) -> (bool, bool, u8) {
if self.is_seen(message_id) {
self.stats.duplicates_ignored += 1;
return (false, false, 0);
}
if ttl == 0 {
self.stats.messages_dropped_ttl += 1;
return (false, false, 0);
}
self.mark_seen(message_id.to_string());
let new_ttl = ttl.saturating_sub(1);
let should_relay = new_ttl > 0;
(true, should_relay, new_ttl)
}
pub fn generate_message_id(node_id: &str) -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("{}:{}", node_id, timestamp)
}
pub fn wrap_for_relay(node_id: &str, message: Message, ttl: Option<u8>) -> Message {
let message_id = Self::generate_message_id(node_id);
Message::MeshRelay {
message_id,
origin: node_id.to_string(),
ttl: ttl.unwrap_or(DEFAULT_MESH_TTL),
inner: Box::new(message),
}
}
}
pub struct Network {
config: TransportConfig,
gossip_config: GossipConfig,
peers: HashMap<SocketAddr, PeerInfo>,
node_id: String,
#[cfg(feature = "coap")]
coap_server: Option<CoapServer>,
#[cfg(feature = "quic")]
quic_server: Option<QuicServer>,
discovery: Option<Discovery>,
pending_rpcs: HashMap<String, PendingRpc>,
mesh_manager: MeshManager,
}
impl Network {
pub fn new(config: TransportConfig, gossip_config: GossipConfig, node_id: String) -> Self {
Self {
config,
gossip_config,
peers: HashMap::new(),
node_id,
#[cfg(feature = "coap")]
coap_server: None,
#[cfg(feature = "quic")]
quic_server: None,
discovery: None,
pending_rpcs: HashMap::new(),
mesh_manager: MeshManager::new(),
}
}
pub async fn start(&mut self) -> Result<()> {
match &self.config {
TransportConfig::Memory => {
log::info!("Starting memory transport (testing only)");
Ok(())
}
#[cfg(feature = "coap")]
TransportConfig::Coap { bind_addr, port } => {
log::info!("Starting CoAP transport on {}:{}", bind_addr, port);
let mut server = CoapServer::new(bind_addr.clone(), *port, self.node_id.clone());
server.start().await?;
if let Err(e) = server.join_multicast() {
log::warn!("Failed to join multicast: {}", e);
}
self.coap_server = Some(server);
Ok(())
}
#[cfg(not(feature = "coap"))]
TransportConfig::Coap { bind_addr, port } => {
log::warn!("CoAP transport requested but feature not enabled");
log::info!("Would start CoAP on {}:{}", bind_addr, port);
Ok(())
}
#[cfg(feature = "quic")]
TransportConfig::Quic { bind_addr, port } => {
use crate::quic::QuicConfig;
log::info!("Starting QUIC transport on {}:{}", bind_addr, port);
let quic_config = QuicConfig {
bind_addr: bind_addr.clone(),
port: *port,
..QuicConfig::default()
};
let mut server = QuicServer::new(quic_config, self.node_id.clone());
server.start().await?;
self.quic_server = Some(server);
Ok(())
}
#[cfg(not(feature = "quic"))]
TransportConfig::Quic { bind_addr, port } => {
log::warn!("QUIC transport requested but feature not enabled");
log::info!("Would start QUIC on {}:{}", bind_addr, port);
Ok(())
}
TransportConfig::Mesh { mode } => {
log::info!("Starting mesh transport: {:?}", mode);
Ok(())
}
#[cfg(feature = "webrtc")]
TransportConfig::WebRtc {
stun_server,
signaling_port,
..
} => {
log::info!(
"Starting WebRTC transport with STUN {} on signaling port {}",
stun_server,
signaling_port
);
Ok(())
}
#[cfg(feature = "ble")]
TransportConfig::Ble {
device_name,
mesh_relay,
tx_power,
} => {
log::info!(
"Starting BLE transport: {} (relay: {}, tx_power: {}dBm)",
device_name,
mesh_relay,
tx_power
);
Ok(())
}
}
}
pub async fn stop(&mut self) -> Result<()> {
log::info!("Stopping network");
if let Some(ref mut discovery) = self.discovery {
discovery.stop()?;
}
self.discovery = None;
#[cfg(feature = "coap")]
if let Some(ref mut server) = self.coap_server {
server.stop().await?;
self.coap_server = None;
}
self.peers.clear();
Ok(())
}
pub fn start_discovery(&mut self, port: u16) -> Result<()> {
let mut discovery = Discovery::new(self.node_id.clone(), port)?;
discovery.register()?;
discovery.start_browsing()?;
self.discovery = Some(discovery);
log::info!("mDNS discovery started");
Ok(())
}
pub fn sync_discovered_peers(&mut self) {
if let Some(ref discovery) = self.discovery {
let discovered = discovery.get_peer_addrs();
for addr in discovered {
if !self.peers.contains_key(&addr) {
log::debug!("Adding discovered peer: {}", addr);
self.add_peer(addr);
}
}
}
}
pub fn discovered_peer_count(&self) -> usize {
self.discovery.as_ref().map(|d| d.peer_count()).unwrap_or(0)
}
#[cfg(feature = "coap")]
pub async fn recv(&self) -> Result<Option<(SocketAddr, Message)>> {
if let Some(ref server) = self.coap_server {
server.recv().await
} else {
Ok(None)
}
}
#[cfg(not(feature = "coap"))]
pub async fn recv(&self) -> Result<Option<(SocketAddr, Message)>> {
Ok(None)
}
pub fn is_running(&self) -> bool {
#[cfg(feature = "coap")]
if let Some(ref server) = self.coap_server {
return server.is_running();
}
false
}
#[cfg(feature = "coap")]
pub fn coap_server(&self) -> Option<&CoapServer> {
self.coap_server.as_ref()
}
#[cfg(feature = "coap")]
pub fn coap_server_mut(&mut self) -> Option<&mut CoapServer> {
self.coap_server.as_mut()
}
pub fn add_peer(&mut self, addr: SocketAddr) {
self.peers.insert(
addr,
PeerInfo {
addr,
last_seen: Instant::now(),
latest_seq: 0,
quality: 50,
},
);
}
pub fn remove_peer(&mut self, addr: &SocketAddr) {
self.peers.remove(addr);
}
pub fn active_peers(&self) -> Vec<&PeerInfo> {
let timeout = Duration::from_secs(300); self.peers
.values()
.filter(|p| p.last_seen.elapsed() < timeout)
.collect()
}
pub fn update_peer(&mut self, addr: SocketAddr, latest_seq: u32) {
if let Some(peer) = self.peers.get_mut(&addr) {
peer.last_seen = Instant::now();
peer.latest_seq = latest_seq;
peer.quality = (peer.quality + 5).min(100);
}
}
pub fn mark_peer_failed(&mut self, addr: &SocketAddr) {
if let Some(peer) = self.peers.get_mut(addr) {
peer.quality = peer.quality.saturating_sub(10);
}
}
#[cfg(feature = "coap")]
pub async fn send(&mut self, addr: &SocketAddr, message: &Message) -> Result<()> {
if let Some(ref mut server) = self.coap_server {
server.send(addr, message, false).await
} else {
let _data = serde_json::to_vec(message)?;
log::debug!("Sending message to {} (no transport): {:?}", addr, message);
Ok(())
}
}
#[cfg(not(feature = "coap"))]
pub async fn send(&mut self, addr: &SocketAddr, message: &Message) -> Result<()> {
let _data = serde_json::to_vec(message)?;
log::debug!("Sending message to {}: {:?}", addr, message);
Ok(())
}
#[cfg(feature = "coap")]
pub async fn send_confirmable(&mut self, addr: &SocketAddr, message: &Message) -> Result<()> {
if let Some(ref mut server) = self.coap_server {
server.send(addr, message, true).await
} else {
self.send(addr, message).await
}
}
#[cfg(not(feature = "coap"))]
pub async fn send_confirmable(&mut self, addr: &SocketAddr, message: &Message) -> Result<()> {
self.send(addr, message).await
}
pub async fn broadcast(&mut self, message: &Message) -> Result<()> {
let peers: Vec<SocketAddr> = self.active_peers().iter().map(|p| p.addr).collect();
for addr in peers {
if let Err(e) = self.send(&addr, message).await {
log::warn!("Failed to send to {}: {}", addr, e);
}
}
Ok(())
}
#[cfg(feature = "coap")]
pub async fn send_discovery(&mut self) -> Result<()> {
if let Some(ref mut server) = self.coap_server {
server.send_discovery().await
} else {
Ok(())
}
}
#[cfg(not(feature = "coap"))]
pub async fn send_discovery(&mut self) -> Result<()> {
log::debug!("Discovery not available without CoAP");
Ok(())
}
#[cfg(feature = "coap")]
pub async fn handle_retransmissions(&mut self) -> Result<()> {
if let Some(ref mut server) = self.coap_server {
server.handle_retransmissions().await
} else {
Ok(())
}
}
#[cfg(not(feature = "coap"))]
pub async fn handle_retransmissions(&mut self) -> Result<()> {
Ok(())
}
pub fn gossip_peers(&self) -> Vec<SocketAddr> {
let mut peers: Vec<_> = self.active_peers().into_iter().collect();
peers.sort_by(|a, b| b.quality.cmp(&a.quality));
peers
.into_iter()
.take(self.gossip_config.max_peers)
.map(|p| p.addr)
.collect()
}
pub fn peer_count(&self) -> usize {
self.active_peers().len()
}
fn generate_request_id() -> String {
let id = REQUEST_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
format!("rpc-{}-{}", std::process::id(), id)
}
#[cfg(feature = "coap")]
pub async fn send_remote_call(
&mut self,
addr: &SocketAddr,
method: &str,
payload: Vec<u8>,
timeout: Duration,
) -> Result<RpcResponse> {
let request_id = Self::generate_request_id();
let (tx, rx) = bounded::<RpcResponse>(1);
self.pending_rpcs.insert(
request_id.clone(),
PendingRpc {
response_tx: tx,
sent_at: Instant::now(),
method: method.to_string(),
},
);
let message = Message::RemoteCall {
request_id: request_id.clone(),
from: self.node_id.clone(),
method: method.to_string(),
payload,
};
if let Err(e) = self.send_confirmable(addr, &message).await {
self.pending_rpcs.remove(&request_id);
return Err(e);
}
log::debug!("RPC call {} to {}: method={}", request_id, addr, method);
match smol::future::or(
async {
rx.recv()
.await
.map_err(|_| Error::network("RPC channel closed".to_string()))
},
async {
smol::Timer::after(timeout).await;
Err(Error::Timeout(format!(
"RPC {} timed out after {:?}",
request_id, timeout
)))
},
)
.await
{
Ok(response) => {
self.pending_rpcs.remove(&request_id);
log::debug!("RPC {} completed: success={}", request_id, response.success);
Ok(response)
}
Err(e) => {
self.pending_rpcs.remove(&request_id);
log::warn!("RPC {} failed: {}", request_id, e);
Err(e)
}
}
}
#[cfg(not(feature = "coap"))]
pub async fn send_remote_call(
&mut self,
_addr: &SocketAddr,
_method: &str,
_payload: Vec<u8>,
_timeout: Duration,
) -> Result<RpcResponse> {
Err(Error::network("RPC requires CoAP feature".to_string()))
}
pub fn handle_rpc_response(&mut self, request_id: &str, success: bool, data: Vec<u8>) {
if let Some(pending) = self.pending_rpcs.remove(request_id) {
let response = RpcResponse { success, data };
let elapsed = pending.sent_at.elapsed();
if let Err(e) = pending.response_tx.try_send(response) {
log::warn!("Failed to deliver RPC response {}: {:?}", request_id, e);
} else {
log::debug!(
"RPC {} response delivered (method={}, elapsed={:?})",
request_id,
pending.method,
elapsed
);
}
} else {
log::warn!(
"Received response for unknown RPC: {} (may have timed out)",
request_id
);
}
}
pub fn cleanup_stale_rpcs(&mut self, max_age: Duration) {
let now = Instant::now();
let stale: Vec<String> = self
.pending_rpcs
.iter()
.filter(|(_, pending)| now.duration_since(pending.sent_at) > max_age)
.map(|(id, _)| id.clone())
.collect();
for request_id in stale {
if let Some(pending) = self.pending_rpcs.remove(&request_id) {
log::warn!(
"RPC {} timed out (method={}, age={:?})",
request_id,
pending.method,
now.duration_since(pending.sent_at)
);
}
}
}
pub fn pending_rpc_count(&self) -> usize {
self.pending_rpcs.len()
}
pub async fn process_mesh_message(
&mut self,
from: SocketAddr,
message_id: &str,
origin: &str,
ttl: u8,
inner: Message,
) -> Option<Message> {
let (should_process, should_relay, new_ttl) =
self.mesh_manager.process_relay(message_id, ttl);
if !should_process {
log::trace!("Mesh message {} skipped (duplicate or TTL=0)", message_id);
return None;
}
if should_relay {
let relay_msg = Message::MeshRelay {
message_id: message_id.to_string(),
origin: origin.to_string(),
ttl: new_ttl,
inner: Box::new(inner.clone()),
};
let peers_to_relay: Vec<SocketAddr> = self
.active_peers()
.iter()
.filter(|p| p.addr != from)
.map(|p| p.addr)
.collect();
for peer_addr in peers_to_relay {
if let Err(e) = self.send(&peer_addr, &relay_msg).await {
log::warn!("Failed to relay to {}: {}", peer_addr, e);
} else {
self.mesh_manager.stats.messages_relayed += 1;
}
}
}
Some(inner)
}
pub async fn mesh_broadcast(&mut self, message: Message) -> Result<usize> {
let wrapped = MeshManager::wrap_for_relay(&self.node_id, message, None);
if let Message::MeshRelay { ref message_id, .. } = wrapped {
self.mesh_manager.mark_seen(message_id.clone());
}
self.mesh_manager.stats.messages_originated += 1;
let peers: Vec<SocketAddr> = self.active_peers().iter().map(|p| p.addr).collect();
let mut sent_count = 0;
for peer_addr in peers {
if let Err(e) = self.send(&peer_addr, &wrapped).await {
log::warn!("Failed to broadcast to {}: {}", peer_addr, e);
} else {
sent_count += 1;
}
}
Ok(sent_count)
}
pub fn mesh_manager(&self) -> &MeshManager {
&self.mesh_manager
}
pub fn mesh_manager_mut(&mut self) -> &mut MeshManager {
&mut self.mesh_manager
}
pub fn mesh_stats(&self) -> &MeshStats {
&self.mesh_manager.stats
}
}
pub struct GossipManager {
config: GossipConfig,
last_gossip: Instant,
pending_announcements: Vec<Hash>,
}
impl GossipManager {
pub fn new(config: GossipConfig) -> Self {
Self {
config,
last_gossip: Instant::now(),
pending_announcements: Vec::new(),
}
}
pub fn should_gossip(&self) -> bool {
self.last_gossip.elapsed() >= self.config.loop_delay
}
pub fn gossip_complete(&mut self, success: bool) {
self.last_gossip = Instant::now();
if success {
} else {
}
}
pub fn announce(&mut self, hash: Hash) {
self.pending_announcements.push(hash);
}
pub fn take_announcements(&mut self) -> Vec<Hash> {
std::mem::take(&mut self.pending_announcements)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_creation() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
assert_eq!(network.peer_count(), 0);
}
#[test]
fn test_peer_management() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
assert_eq!(network.peer_count(), 1);
network.remove_peer(&addr);
assert_eq!(network.peer_count(), 0);
}
#[test]
fn test_gossip_timing() {
let mut config = GossipConfig::default();
config.loop_delay = Duration::from_millis(10);
let mut gossip = GossipManager::new(config);
assert!(!gossip.should_gossip());
std::thread::sleep(Duration::from_millis(20));
assert!(gossip.should_gossip());
gossip.gossip_complete(true);
assert!(!gossip.should_gossip());
}
#[test]
fn test_peer_info_struct() {
let addr: SocketAddr = "192.168.1.1:5683".parse().unwrap();
let peer = PeerInfo {
addr,
last_seen: Instant::now(),
latest_seq: 42,
quality: 75,
};
assert_eq!(peer.addr, addr);
assert_eq!(peer.latest_seq, 42);
assert_eq!(peer.quality, 75);
}
#[test]
fn test_peer_info_clone() {
let addr: SocketAddr = "10.0.0.1:1234".parse().unwrap();
let peer1 = PeerInfo {
addr,
last_seen: Instant::now(),
latest_seq: 100,
quality: 90,
};
let peer2 = peer1.clone();
assert_eq!(peer1.addr, peer2.addr);
assert_eq!(peer1.latest_seq, peer2.latest_seq);
assert_eq!(peer1.quality, peer2.quality);
}
#[test]
fn test_peer_info_debug() {
let addr: SocketAddr = "127.0.0.1:5683".parse().unwrap();
let peer = PeerInfo {
addr,
last_seen: Instant::now(),
latest_seq: 0,
quality: 50,
};
let debug_str = format!("{:?}", peer);
assert!(debug_str.contains("PeerInfo"));
assert!(debug_str.contains("127.0.0.1:5683"));
}
#[test]
fn test_message_ping_serialize() {
let msg = Message::Ping {
node_id: "node123".to_string(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("Ping"));
assert!(json.contains("node123"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::Ping { node_id } = parsed {
assert_eq!(node_id, "node123");
} else {
panic!("Expected Ping message");
}
}
#[test]
fn test_message_pong_serialize() {
let msg = Message::Pong {
node_id: "node456".to_string(),
latest_seq: 42,
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("Pong"));
assert!(json.contains("42"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::Pong {
node_id,
latest_seq,
} = parsed
{
assert_eq!(node_id, "node456");
assert_eq!(latest_seq, 42);
} else {
panic!("Expected Pong message");
}
}
#[test]
fn test_message_gossip_request_serialize() {
let msg = Message::GossipRequest {
from_seq: 10,
limit: 50,
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("GossipRequest"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::GossipRequest { from_seq, limit } = parsed {
assert_eq!(from_seq, 10);
assert_eq!(limit, 50);
} else {
panic!("Expected GossipRequest message");
}
}
#[test]
fn test_message_gossip_response_serialize() {
let msg = Message::GossipResponse { records: vec![] };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("GossipResponse"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::GossipResponse { records } = parsed {
assert!(records.is_empty());
} else {
panic!("Expected GossipResponse message");
}
}
#[test]
fn test_message_new_record_serialize() {
let hash = Hash::from_raw(&[0u8; 32]);
let msg = Message::NewRecord { hash: hash.clone() };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("NewRecord"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::NewRecord { hash: h } = parsed {
assert_eq!(h, hash);
} else {
panic!("Expected NewRecord message");
}
}
#[test]
fn test_message_get_record_serialize() {
let hash = Hash::from_raw(&[0u8; 32]);
let msg = Message::GetRecord { hash: hash.clone() };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("GetRecord"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::GetRecord { hash: h } = parsed {
assert_eq!(h, hash);
} else {
panic!("Expected GetRecord message");
}
}
#[test]
fn test_message_clone() {
let msg1 = Message::Ping {
node_id: "test".to_string(),
};
let msg2 = msg1.clone();
if let (Message::Ping { node_id: n1 }, Message::Ping { node_id: n2 }) = (&msg1, &msg2) {
assert_eq!(n1, n2);
} else {
panic!("Clone failed");
}
}
#[test]
fn test_message_debug() {
let msg = Message::Ping {
node_id: "debug-test".to_string(),
};
let debug_str = format!("{:?}", msg);
assert!(debug_str.contains("Ping"));
assert!(debug_str.contains("debug-test"));
}
#[test]
fn test_network_update_peer() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
network.update_peer(addr, 100);
let peers = network.active_peers();
assert_eq!(peers.len(), 1);
assert_eq!(peers[0].latest_seq, 100);
assert!(peers[0].quality > 50);
}
#[test]
fn test_network_update_nonexistent_peer() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.update_peer(addr, 100);
assert_eq!(network.peer_count(), 0);
}
#[test]
fn test_network_mark_peer_failed() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
let initial_quality = network.active_peers()[0].quality;
network.mark_peer_failed(&addr);
let peers = network.active_peers();
assert!(peers[0].quality < initial_quality);
}
#[test]
fn test_network_mark_nonexistent_peer_failed() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.mark_peer_failed(&addr);
}
#[test]
fn test_network_active_peers_timeout() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
let active = network.active_peers();
assert_eq!(active.len(), 1);
}
#[test]
fn test_network_gossip_peers_sorting() {
let config = TransportConfig::Memory;
let mut gossip = GossipConfig::default();
gossip.max_peers = 10;
let mut network = Network::new(config, gossip, "test-node".to_string());
for i in 0..5 {
let addr: SocketAddr = format!("127.0.0.1:808{}", i).parse().unwrap();
network.add_peer(addr);
}
let addr1: SocketAddr = "127.0.0.1:8081".parse().unwrap();
network.update_peer(addr1, 10);
network.update_peer(addr1, 20);
let gossip_peers = network.gossip_peers();
assert!(!gossip_peers.is_empty());
}
#[test]
fn test_network_gossip_peers_limited() {
let config = TransportConfig::Memory;
let mut gossip = GossipConfig::default();
gossip.max_peers = 2;
let mut network = Network::new(config, gossip, "test-node".to_string());
for i in 0..5 {
let addr: SocketAddr = format!("127.0.0.1:808{}", i).parse().unwrap();
network.add_peer(addr);
}
let gossip_peers = network.gossip_peers();
assert_eq!(gossip_peers.len(), 2); }
#[test]
fn test_network_is_running_false() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
assert!(!network.is_running());
}
#[test]
fn test_network_discovered_peer_count_no_discovery() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
assert_eq!(network.discovered_peer_count(), 0);
}
#[test]
fn test_gossip_manager_creation() {
let config = GossipConfig::default();
let manager = GossipManager::new(config);
assert!(manager.pending_announcements.is_empty());
}
#[test]
fn test_gossip_manager_announce() {
let config = GossipConfig::default();
let mut manager = GossipManager::new(config);
let hash = Hash::from_raw(&[1u8; 32]);
manager.announce(hash.clone());
assert_eq!(manager.pending_announcements.len(), 1);
}
#[test]
fn test_gossip_manager_take_announcements() {
let config = GossipConfig::default();
let mut manager = GossipManager::new(config);
let hash1 = Hash::from_raw(&[1u8; 32]);
let hash2 = Hash::from_raw(&[2u8; 32]);
manager.announce(hash1);
manager.announce(hash2);
let announcements = manager.take_announcements();
assert_eq!(announcements.len(), 2);
assert!(manager.pending_announcements.is_empty());
assert!(manager.take_announcements().is_empty());
}
#[test]
fn test_gossip_manager_complete_failure() {
let mut config = GossipConfig::default();
config.loop_delay = Duration::from_millis(10);
let mut manager = GossipManager::new(config);
std::thread::sleep(Duration::from_millis(20));
assert!(manager.should_gossip());
manager.gossip_complete(false);
assert!(!manager.should_gossip());
}
#[test]
fn test_network_quality_caps_at_100() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
for _ in 0..50 {
network.update_peer(addr, 1);
}
let peers = network.active_peers();
assert_eq!(peers[0].quality, 100); }
#[test]
fn test_network_quality_floor_at_0() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap();
network.add_peer(addr);
for _ in 0..20 {
network.mark_peer_failed(&addr);
}
let peers = network.active_peers();
assert_eq!(peers[0].quality, 0); }
#[test]
fn test_network_multiple_peers() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addrs: Vec<SocketAddr> = vec![
"127.0.0.1:19080".parse().unwrap(),
"127.0.0.1:8081".parse().unwrap(),
"127.0.0.1:8082".parse().unwrap(),
];
for addr in &addrs {
network.add_peer(*addr);
}
assert_eq!(network.peer_count(), 3);
network.remove_peer(&addrs[1]);
assert_eq!(network.peer_count(), 2);
}
#[test]
fn test_message_record_data_serialize() {
use crate::types::{
Action, ActionType, AgentPubKey, Entry, EntryType, Signature, Timestamp,
};
let record = Record {
action: Action {
action_type: ActionType::Create,
author: AgentPubKey([0u8; 32]),
timestamp: Timestamp(1234567890),
seq: 1,
prev_action: None,
entry_hash: None,
signature: Signature([0u8; 64]),
},
entry: Some(Entry {
entry_type: EntryType::App,
content: vec![1, 2, 3],
}),
};
let msg = Message::RecordData { record };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("RecordData"));
let parsed: Message = serde_json::from_str(&json).unwrap();
assert!(matches!(parsed, Message::RecordData { .. }));
}
#[test]
fn test_network_sync_discovered_peers_no_discovery() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
network.sync_discovered_peers();
assert_eq!(network.peer_count(), 0);
}
#[test]
fn test_transport_config_memory() {
let config = TransportConfig::Memory;
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("Memory"));
}
#[test]
fn test_transport_config_coap() {
let config = TransportConfig::Coap {
bind_addr: "0.0.0.0".to_string(),
port: 5683,
};
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("Coap"));
assert!(debug_str.contains("5683"));
}
#[test]
fn test_transport_config_quic() {
let config = TransportConfig::Quic {
bind_addr: "0.0.0.0".to_string(),
port: 5684,
};
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("Quic"));
}
#[test]
fn test_transport_config_mesh() {
use crate::config::MeshMode;
let config = TransportConfig::Mesh {
mode: MeshMode::WiFiDirect,
};
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("Mesh"));
assert!(debug_str.contains("WiFiDirect"));
}
#[test]
fn test_network_gossip_manager_multiple_announces() {
let config = GossipConfig::default();
let mut manager = GossipManager::new(config);
for i in 0..10 {
let mut bytes = [0u8; 32];
bytes[0] = i;
manager.announce(Hash::from_raw(&bytes));
}
assert_eq!(manager.pending_announcements.len(), 10);
let taken = manager.take_announcements();
assert_eq!(taken.len(), 10);
assert!(manager.pending_announcements.is_empty());
}
#[test]
fn test_network_start_memory_transport() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.start().await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_stop() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
network.start().await.unwrap();
let result = network.stop().await;
assert!(result.is_ok());
assert_eq!(network.peer_count(), 0);
});
}
#[test]
fn test_network_recv_memory() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.recv().await;
assert!(result.is_ok());
assert!(result.unwrap().is_none());
});
}
#[test]
fn test_network_send_memory() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let addr: SocketAddr = "127.0.0.1:5683".parse().unwrap();
let msg = Message::Ping {
node_id: "test".to_string(),
};
let result = network.send(&addr, &msg).await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_send_confirmable_memory() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let addr: SocketAddr = "127.0.0.1:5683".parse().unwrap();
let msg = Message::Ping {
node_id: "test".to_string(),
};
let result = network.send_confirmable(&addr, &msg).await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_broadcast() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap();
let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap();
network.add_peer(addr1);
network.add_peer(addr2);
smol::block_on(async {
let msg = Message::Ping {
node_id: "broadcast".to_string(),
};
let result = network.broadcast(&msg).await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_send_discovery_memory() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.send_discovery().await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_handle_retransmissions_memory() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.handle_retransmissions().await;
assert!(result.is_ok());
});
}
#[test]
fn test_network_start_quic() {
let config = TransportConfig::Quic {
bind_addr: "127.0.0.1".to_string(),
port: 15684, };
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.start().await;
#[cfg(not(feature = "quic"))]
assert!(result.is_ok());
#[cfg(feature = "quic")]
{
let _ = result;
}
});
}
#[test]
fn test_network_start_mesh() {
use crate::config::MeshMode;
let config = TransportConfig::Mesh {
mode: MeshMode::BluetoothLE,
};
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let result = network.start().await;
assert!(result.is_ok());
});
}
#[test]
fn test_gossip_manager_config() {
let mut config = GossipConfig::default();
config.loop_delay = Duration::from_millis(100);
config.max_peers = 5;
let manager = GossipManager::new(config.clone());
assert_eq!(manager.config.loop_delay, Duration::from_millis(100));
assert_eq!(manager.config.max_peers, 5);
}
#[test]
fn test_rpc_response_struct() {
let response = RpcResponse {
success: true,
data: vec![1, 2, 3, 4],
};
assert!(response.success);
assert_eq!(response.data, vec![1, 2, 3, 4]);
}
#[test]
fn test_rpc_response_clone() {
let response1 = RpcResponse {
success: false,
data: b"error message".to_vec(),
};
let response2 = response1.clone();
assert_eq!(response1.success, response2.success);
assert_eq!(response1.data, response2.data);
}
#[test]
fn test_rpc_response_debug() {
let response = RpcResponse {
success: true,
data: vec![],
};
let debug_str = format!("{:?}", response);
assert!(debug_str.contains("RpcResponse"));
assert!(debug_str.contains("success: true"));
}
#[test]
fn test_generate_request_id() {
let id1 = Network::generate_request_id();
let id2 = Network::generate_request_id();
assert_ne!(id1, id2);
assert!(id1.starts_with("rpc-"));
assert!(id2.starts_with("rpc-"));
}
#[test]
fn test_pending_rpc_count() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
assert_eq!(network.pending_rpc_count(), 0);
}
#[test]
fn test_handle_rpc_response_unknown_request() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
network.handle_rpc_response("unknown-id", true, vec![1, 2, 3]);
assert_eq!(network.pending_rpc_count(), 0);
}
#[test]
fn test_cleanup_stale_rpcs_empty() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
network.cleanup_stale_rpcs(Duration::from_secs(10));
assert_eq!(network.pending_rpc_count(), 0);
}
#[test]
fn test_message_remote_call_serialize() {
let msg = Message::RemoteCall {
request_id: "rpc-123".to_string(),
from: "node-abc".to_string(),
method: "get_status".to_string(),
payload: b"{}".to_vec(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("RemoteCall"));
assert!(json.contains("rpc-123"));
assert!(json.contains("get_status"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::RemoteCall {
request_id,
from,
method,
payload,
} = parsed
{
assert_eq!(request_id, "rpc-123");
assert_eq!(from, "node-abc");
assert_eq!(method, "get_status");
assert_eq!(payload, b"{}".to_vec());
} else {
panic!("Expected RemoteCall message");
}
}
#[test]
fn test_message_remote_call_response_serialize() {
let msg = Message::RemoteCallResponse {
request_id: "rpc-456".to_string(),
success: true,
data: b"result data".to_vec(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("RemoteCallResponse"));
assert!(json.contains("rpc-456"));
assert!(json.contains("true"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::RemoteCallResponse {
request_id,
success,
data,
} = parsed
{
assert_eq!(request_id, "rpc-456");
assert!(success);
assert_eq!(data, b"result data".to_vec());
} else {
panic!("Expected RemoteCallResponse message");
}
}
#[test]
fn test_message_remote_call_response_failure() {
let msg = Message::RemoteCallResponse {
request_id: "rpc-789".to_string(),
success: false,
data: b"method not found".to_vec(),
};
let json = serde_json::to_string(&msg).unwrap();
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::RemoteCallResponse { success, data, .. } = parsed {
assert!(!success);
assert_eq!(String::from_utf8_lossy(&data), "method not found");
} else {
panic!("Expected RemoteCallResponse message");
}
}
#[test]
fn test_send_remote_call_without_coap() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let mut network = Network::new(config, gossip, "test-node".to_string());
smol::block_on(async {
let addr: SocketAddr = "127.0.0.1:5683".parse().unwrap();
let result = network
.send_remote_call(&addr, "test_method", vec![], Duration::from_secs(1))
.await;
assert!(result.is_err());
});
}
#[test]
fn test_mesh_manager_new() {
let mesh = MeshManager::new();
assert_eq!(mesh.stats.messages_relayed, 0);
assert_eq!(mesh.stats.duplicates_ignored, 0);
}
#[test]
fn test_mesh_manager_seen_tracking() {
let mut mesh = MeshManager::new();
assert!(!mesh.is_seen("msg-1"));
mesh.mark_seen("msg-1".to_string());
assert!(mesh.is_seen("msg-1"));
assert!(!mesh.is_seen("msg-2"));
}
#[test]
fn test_mesh_manager_process_relay_new() {
let mut mesh = MeshManager::new();
let (should_process, should_relay, new_ttl) = mesh.process_relay("msg-1", 5);
assert!(should_process);
assert!(should_relay);
assert_eq!(new_ttl, 4);
}
#[test]
fn test_mesh_manager_process_relay_duplicate() {
let mut mesh = MeshManager::new();
let (should_process, _, _) = mesh.process_relay("msg-1", 5);
assert!(should_process);
let (should_process, should_relay, _) = mesh.process_relay("msg-1", 5);
assert!(!should_process);
assert!(!should_relay);
assert_eq!(mesh.stats.duplicates_ignored, 1);
}
#[test]
fn test_mesh_manager_process_relay_ttl_zero() {
let mut mesh = MeshManager::new();
let (should_process, should_relay, _) = mesh.process_relay("msg-1", 0);
assert!(!should_process);
assert!(!should_relay);
assert_eq!(mesh.stats.messages_dropped_ttl, 1);
}
#[test]
fn test_mesh_manager_process_relay_ttl_one() {
let mut mesh = MeshManager::new();
let (should_process, should_relay, new_ttl) = mesh.process_relay("msg-1", 1);
assert!(should_process);
assert!(!should_relay); assert_eq!(new_ttl, 0);
}
#[test]
fn test_mesh_manager_generate_message_id() {
let id1 = MeshManager::generate_message_id("node-1");
let id2 = MeshManager::generate_message_id("node-1");
assert!(id1.starts_with("node-1:"));
assert!(id2.starts_with("node-1:"));
assert_ne!(id1, id2); }
#[test]
fn test_mesh_manager_wrap_for_relay() {
let inner = Message::Ping {
node_id: "source".to_string(),
};
let wrapped = MeshManager::wrap_for_relay("relay-node", inner, Some(3));
if let Message::MeshRelay {
message_id,
origin,
ttl,
inner,
} = wrapped
{
assert!(message_id.starts_with("relay-node:"));
assert_eq!(origin, "relay-node");
assert_eq!(ttl, 3);
assert!(matches!(*inner, Message::Ping { .. }));
} else {
panic!("Expected MeshRelay message");
}
}
#[test]
fn test_mesh_stats_default() {
let stats = MeshStats::default();
assert_eq!(stats.messages_relayed, 0);
assert_eq!(stats.messages_dropped_ttl, 0);
assert_eq!(stats.duplicates_ignored, 0);
assert_eq!(stats.messages_originated, 0);
}
#[test]
fn test_network_mesh_manager_access() {
let config = TransportConfig::Memory;
let gossip = GossipConfig::default();
let network = Network::new(config, gossip, "test-node".to_string());
let mesh = network.mesh_manager();
assert_eq!(mesh.stats.messages_relayed, 0);
}
#[test]
fn test_message_mesh_relay_serialize() {
let inner = Message::NewRecord {
hash: Hash([0u8; 32]),
};
let msg = Message::MeshRelay {
message_id: "node-1:123".to_string(),
origin: "node-1".to_string(),
ttl: 4,
inner: Box::new(inner),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("MeshRelay"));
assert!(json.contains("node-1:123"));
assert!(json.contains("ttl"));
let parsed: Message = serde_json::from_str(&json).unwrap();
if let Message::MeshRelay {
message_id,
origin,
ttl,
inner,
} = parsed
{
assert_eq!(message_id, "node-1:123");
assert_eq!(origin, "node-1");
assert_eq!(ttl, 4);
assert!(matches!(*inner, Message::NewRecord { .. }));
} else {
panic!("Expected MeshRelay message");
}
}
}