use crate::{
router::SynapseRouter,
config::Config,
types::{MessageType, SecurityLevel, SimpleMessage},
error::Result,
};
use crate::synapse::blockchain::serialization::UuidWrapper;
use uuid::Uuid;
use std::time::Duration;
use tokio::time::sleep;
use tracing::{info, warn, debug, error};
use uuid;
pub struct ConnectivityManager {
router: SynapseRouter,
polling_interval: Duration,
adaptive_polling: bool,
backup_email_providers: Vec<EmailProviderConfig>,
}
#[derive(Debug, Clone)]
pub struct EmailProviderConfig {
pub name: String,
pub smtp_host: String,
pub smtp_port: u16,
pub imap_host: String,
pub imap_port: u16,
pub supports_ipv6: bool,
pub supports_ipv4: bool,
}
impl ConnectivityManager {
pub fn new(router: SynapseRouter) -> Self {
Self {
router,
polling_interval: Duration::from_secs(30), adaptive_polling: true,
backup_email_providers: Self::default_backup_providers(),
}
}
pub async fn start_adaptive_polling(&mut self) -> Result<()> {
info!("Starting adaptive polling for network-constrained environments");
let mut polling_interval = self.polling_interval;
let mut consecutive_failures = 0;
let mut last_message_time = std::time::Instant::now();
loop {
sleep(polling_interval).await;
match self.router.receive_messages().await {
Ok(messages) => {
if !messages.is_empty() {
info!("Received {} messages via polling", messages.len());
last_message_time = std::time::Instant::now();
consecutive_failures = 0;
for message in messages {
debug!("Polled message from {}: {}", message.from_entity, message.content);
}
if self.adaptive_polling {
polling_interval = Duration::from_secs(10); }
} else {
if self.adaptive_polling {
let time_since_last = last_message_time.elapsed();
if time_since_last > Duration::from_secs(600) { polling_interval = Duration::from_secs(120); }
}
}
}
Err(e) => {
consecutive_failures += 1;
error!("Failed to poll messages (attempt {}): {}", consecutive_failures, e);
if consecutive_failures >= 3 {
warn!("Primary email provider failing, attempting backup providers");
if let Err(backup_error) = self.try_backup_providers().await {
error!("All backup providers failed: {}", backup_error);
}
}
let backoff_duration = Duration::from_secs(std::cmp::min(300, 30 * consecutive_failures));
sleep(backoff_duration).await;
}
}
}
}
async fn try_backup_providers(&self) -> Result<()> {
for provider in &self.backup_email_providers {
info!("Attempting backup provider: {}", provider.name);
debug!("Testing connectivity to {}:{}", provider.smtp_host, provider.smtp_port);
}
Ok(())
}
pub async fn send_with_fallback(
&self,
to_entity: &str,
content: &str,
message_type: MessageType,
security_level: SecurityLevel,
) -> Result<String> {
use std::collections::HashMap;
let message_id = UuidWrapper::new(Uuid::new_v4()).to_string();
let simple_msg = SimpleMessage {
to: to_entity.to_string(),
from_entity: self.router.get_our_global_id().to_string(),
content: content.to_string(),
message_type,
metadata: {
let mut meta = HashMap::new();
meta.insert("message_id".to_string(), message_id.clone());
meta.insert("security_level".to_string(), security_level.to_string());
meta
},
};
match self.router.send_message(simple_msg.clone(), to_entity.to_string()).await {
Ok(_) => {
info!("Message sent successfully via primary provider");
return Ok(message_id);
}
Err(primary_error) => {
warn!("Primary provider failed: {}", primary_error);
}
}
for provider in &self.backup_email_providers {
info!("Attempting to send via backup provider: {}", provider.name);
match self.router.send_message(simple_msg.clone(), to_entity.to_string()).await {
Ok(_) => {
info!("Message sent successfully via backup provider: {}", provider.name);
return Ok(message_id.clone());
}
Err(e) => {
warn!("Backup provider {} failed: {}", provider.name, e);
}
}
}
Err(crate::error::SynapseError::NetworkError("All email providers failed".to_string()))
}
fn default_backup_providers() -> Vec<EmailProviderConfig> {
vec![
EmailProviderConfig {
name: "Gmail".to_string(),
smtp_host: "smtp.gmail.com".to_string(),
smtp_port: 587,
imap_host: "imap.gmail.com".to_string(),
imap_port: 993,
supports_ipv6: true,
supports_ipv4: true,
},
EmailProviderConfig {
name: "Outlook".to_string(),
smtp_host: "smtp-mail.outlook.com".to_string(),
smtp_port: 587,
imap_host: "outlook.office365.com".to_string(),
imap_port: 993,
supports_ipv6: true,
supports_ipv4: true,
},
EmailProviderConfig {
name: "ProtonMail".to_string(),
smtp_host: "127.0.0.1".to_string(), smtp_port: 1025,
imap_host: "127.0.0.1".to_string(),
imap_port: 1143,
supports_ipv6: false,
supports_ipv4: true,
},
EmailProviderConfig {
name: "Yahoo".to_string(),
smtp_host: "smtp.mail.yahoo.com".to_string(),
smtp_port: 587,
imap_host: "imap.mail.yahoo.com".to_string(),
imap_port: 993,
supports_ipv6: true,
supports_ipv4: true,
},
]
}
pub async fn detect_network_constraints(&self) -> NetworkConstraints {
let mut constraints = NetworkConstraints::default();
constraints.has_ipv4 = self.test_ipv4_connectivity().await;
constraints.has_ipv6 = self.test_ipv6_connectivity().await;
constraints.behind_nat = self.detect_nat().await;
constraints.firewall_restrictions = self.detect_firewall_restrictions().await;
info!("Network constraints detected: {:?}", constraints);
constraints
}
async fn test_ipv4_connectivity(&self) -> bool {
debug!("Testing IPv4 connectivity...");
true }
async fn test_ipv6_connectivity(&self) -> bool {
debug!("Testing IPv6 connectivity...");
false }
async fn detect_nat(&self) -> bool {
debug!("Detecting NAT configuration...");
true }
async fn detect_firewall_restrictions(&self) -> Vec<String> {
debug!("Detecting firewall restrictions...");
vec!["inbound_blocked".to_string()] }
pub fn get_recommended_config(&self, constraints: &NetworkConstraints) -> ConnectivityRecommendations {
let mut recommendations = ConnectivityRecommendations::default();
if constraints.behind_nat {
recommendations.use_email_only = true;
recommendations.polling_interval = Duration::from_secs(30);
recommendations.message = "Behind NAT: Using email-only mode with regular polling".to_string();
}
if !constraints.has_ipv4 && constraints.has_ipv6 {
recommendations.preferred_providers = self.backup_email_providers
.iter()
.filter(|p| p.supports_ipv6)
.map(|p| p.name.clone())
.collect();
recommendations.message = "IPv6-only: Using IPv6-capable email providers".to_string();
}
if constraints.firewall_restrictions.contains(&"inbound_blocked".to_string()) {
recommendations.use_email_only = true;
recommendations.message = "Firewall restrictions: Email-only mode recommended".to_string();
}
recommendations
}
}
#[derive(Debug, Default)]
pub struct NetworkConstraints {
pub has_ipv4: bool,
pub has_ipv6: bool,
pub behind_nat: bool,
pub firewall_restrictions: Vec<String>,
}
#[derive(Debug, Default)]
pub struct ConnectivityRecommendations {
pub use_email_only: bool,
pub polling_interval: Duration,
pub preferred_providers: Vec<String>,
pub message: String,
}
pub trait ConnectivityConfigExt {
fn for_constrained_network(entity_name: &str, entity_type: &str) -> Config;
fn for_ipv6_only(entity_name: &str, entity_type: &str) -> Config;
fn with_backup_providers(entity_name: &str, entity_type: &str) -> Config;
}
impl ConnectivityConfigExt for Config {
fn for_constrained_network(entity_name: &str, entity_type: &str) -> Config {
let mut config = Config::gmail_config(entity_name, entity_type,
&format!("{}@gmail.com", entity_name.to_lowercase()),
"app_password");
config.router.idle_timeout = 30; config.router.max_retries = 5; config.router.connection_timeout = 60;
config
}
fn for_ipv6_only(entity_name: &str, entity_type: &str) -> Config {
Config::gmail_config(entity_name, entity_type,
&format!("{}@gmail.com", entity_name.to_lowercase()),
"app_password")
}
fn with_backup_providers(entity_name: &str, entity_type: &str) -> Config {
let config = Config::gmail_config(entity_name, entity_type,
&format!("{}@gmail.com", entity_name.to_lowercase()),
"app_password");
config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_network_constraint_detection() {
let config = Config::default_for_entity("test", "tool");
let router = SynapseRouter::new(config, "test@example.com".to_string()).await.unwrap();
let connectivity_manager = ConnectivityManager::new(router);
let constraints = connectivity_manager.detect_network_constraints().await;
assert!(constraints.behind_nat);
assert!(constraints.has_ipv4);
}
#[test]
fn test_constrained_network_config() {
let config = Config::for_constrained_network("test_entity", "ai_model");
assert_eq!(config.entity.local_name, "test_entity");
assert_eq!(config.router.max_retries, 5);
assert_eq!(config.router.idle_timeout, 30);
}
#[test]
fn test_backup_providers() {
let backup_providers = ConnectivityManager::default_backup_providers();
assert!(!backup_providers.is_empty());
assert!(backup_providers.iter().any(|p| p.name == "Gmail"));
assert!(backup_providers.iter().any(|p| p.supports_ipv6));
}
}