use crate::{ data::user::UserStatus, get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, util::{ crypto_helper::{decrypt_b64, secret_key_to_base64}, file_util::load_file_vec, logger::PrintType, }, }; use dashmap::DashMap; use once_cell::sync::Lazy; use std::{collections::HashMap, env, sync::Arc, time::Duration}; use tokio::{ sync::{Mutex, RwLock, mpsc, watch}, task::JoinHandle, time::{Instant, sleep}, }; use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; use ttp_native::{Policy, Receiver, SendMode, Sender}; use uuid::Uuid; // ============================================================================ // Configuration // ============================================================================ const OMEGA_HOST_DEFAULT: &str = "tensamin.net"; const OMEGA_PORT_DEFAULT: u16 = 9187; fn omega_host_and_port() -> (String, u16) { let host = env::var("OMEGA_HOST") .map(|s| s.trim().to_string()) .unwrap_or_else(|_| OMEGA_HOST_DEFAULT.to_string()); let port = env::var("OMEGA_PORT") .ok() .and_then(|s| s.trim().parse().ok()) .unwrap_or(OMEGA_PORT_DEFAULT); (host, port) } const RECONNECT_DELAY: Duration = Duration::from_secs(5); const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); const TASK_MAX_AGE: Duration = Duration::from_secs(60); // ============================================================================ // Waiting Task System // ============================================================================ pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, } pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); pub fn start_task_cleanup_loop() { tokio::spawn(async { loop { sleep(TASK_CLEANUP_INTERVAL).await; WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); } }); } // ============================================================================ // Connection State // ============================================================================ #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum ConnectionState { Disconnected, Connecting, Connected { identified: bool }, } #[allow(unused_variables)] impl ConnectionState { pub fn is_connected(&self) -> bool { match self { ConnectionState::Connected { identified } => true, _ => false, } } #[allow(dead_code)] pub fn is_identified(&self) -> bool { match self { ConnectionState::Connected { identified: true } => true, _ => false, } } } // ============================================================================ // Omega Connection (Client-side with auto-reconnect) // ============================================================================ #[allow(dead_code)] pub struct OmegaConnection { state: Arc>, sender: Arc>>>, connection_loop_handle: Arc>>>, host: String, port: u16, server_cert: Vec, last_ping: Arc>, heartbeat_handle: Arc>>>, message_send_times: Arc>>, pub connection_id: Uuid, shutdown_tx: Arc>>>, // Track if we should reconnect on close reconnect_on_close: Arc>, } impl OmegaConnection { pub fn new() -> Self { let (host, port) = omega_host_and_port(); Self::with_host(&host, port) } pub fn with_host(host: &str, port: u16) -> Self { // Load server certificate from default location let server_cert = load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); Self::with_host_and_cert(host, port, server_cert) } // New constructor that accepts certificate directly pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { let (shutdown_tx, _) = watch::channel(false); OmegaConnection { state: Arc::new(RwLock::new(ConnectionState::Disconnected)), sender: Arc::new(RwLock::new(None)), connection_loop_handle: Arc::new(Mutex::new(None)), host: host.to_string(), port, server_cert, last_ping: Arc::new(Mutex::new(-1)), heartbeat_handle: Arc::new(Mutex::new(None)), message_send_times: Arc::new(Mutex::new(HashMap::new())), connection_id: Uuid::new_v4(), shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), reconnect_on_close: Arc::new(RwLock::new(true)), } } // ------------------------------------------------------------------------- // Connection Management // ------------------------------------------------------------------------- pub async fn start(self: Arc) { // Cancel any existing connection loop if let Some(handle) = self.connection_loop_handle.lock().await.take() { handle.abort(); } // Recreate shutdown_tx if it was taken by stop() if self.shutdown_tx.lock().await.is_none() { let (shutdown_tx, _) = watch::channel(false); *self.shutdown_tx.lock().await = Some(shutdown_tx); } // Set reconnect flag *self.reconnect_on_close.write().await = true; let self_clone = self.clone(); let handle = tokio::spawn(async move { self_clone.connection_loop().await; }); *self.connection_loop_handle.lock().await = Some(handle); } #[allow(dead_code)] pub async fn stop(&self) { // Disable reconnection *self.reconnect_on_close.write().await = false; if let Some(tx) = self.shutdown_tx.lock().await.take() { let _ = tx.send(true); } if let Some(handle) = self.connection_loop_handle.lock().await.take() { handle.abort(); } if let Some(handle) = self.heartbeat_handle.lock().await.take() { handle.abort(); } // Close sender if connected if let Some(sender) = self.sender.read().await.as_ref() { sender.close(); } *self.state.write().await = ConnectionState::Disconnected; *self.sender.write().await = None; } async fn connection_loop(self: Arc) { let mut reconnect_delay = RECONNECT_DELAY; let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); let mut shutdown_rx = shutdown_rx; loop { if *shutdown_rx.borrow() { log_in!(0, PrintType::Omega, "Connection loop shutting down"); break; } // Check if reconnection is enabled if !*self.reconnect_on_close.read().await { log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); break; } match self.clone().connect_once().await { Ok(()) => { // Connection closed gracefully, check if we should reconnect if *self.reconnect_on_close.read().await { log_err!( 0, PrintType::Omega, "Connection lost, reconnecting in {:?}...", reconnect_delay ); } else { log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); break; } } Err(e) => { log_err!( 0, PrintType::Omega, "Connection failed: {}, retrying in {:?}...", e, reconnect_delay ); } } tokio::select! { _ = sleep(reconnect_delay) => {} _ = shutdown_rx.changed() => { if *shutdown_rx.borrow() { break; } } } reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); } } async fn connect_once(self: Arc) -> Result<(), String> { *self.state.write().await = ConnectionState::Connecting; let addr_str = format!("https://{}:{}", self.host, self.port); let (sender, mut receiver) = ttp_native::client::connect( &addr_str, None, Policy { send_mode: SendMode::SingleStreamPerMessage, max_message_size: 1_000_000_000, close_frame_len: u32::MAX, application_close_code: 0, open_stream_timeout: Duration::from_millis(2_000), write_timeout: Duration::from_millis(2_000), accept_stream_timeout: Duration::from_millis(10_000), read_timeout: Duration::from_millis(30_000), keep_alive_interval: Some(Duration::from_secs(6)), max_idle_timeout: Some(Duration::from_secs(30)), force_close_delay: Duration::from_millis(300), max_transient_recv_errors: 20, transient_recv_backoff: Duration::from_millis(100), receiver_queue_capacity: 1000, }, ) .await .map_err(|e| format!("Connection failed: {}", e))?; log_in!( 0, PrintType::Omega, "QUIC connection established to {}", addr_str ); // Store sender let sender_arc = Arc::new(sender); *self.sender.write().await = Some(sender_arc.clone()); *self.state.write().await = ConnectionState::Connected { identified: false }; // Get handle for close monitoring let sender_handle = sender_arc.handle().clone(); // Start read loop let read_self = self.clone(); let read_handle = tokio::spawn(async move { read_self.read_loop(&mut receiver, sender_handle).await; }); // Send identification self.send_identification().await; // Start heartbeat let heartbeat_self = self.clone(); let heartbeat_handle = tokio::spawn(async move { heartbeat_self.heartbeat_loop().await; }); *self.heartbeat_handle.lock().await = Some(heartbeat_handle); // Wait for read loop to complete (connection closed) let result = read_handle.await; // Cleanup *self.sender.write().await = None; *self.state.write().await = ConnectionState::Disconnected; if let Some(handle) = self.heartbeat_handle.lock().await.take() { handle.abort(); } match result { Ok(()) => { // Check if we should reconnect if *self.reconnect_on_close.read().await { Err("Connection closed, will reconnect".to_string()) } else { Ok(()) } } Err(e) => Err(format!("Read loop error: {}", e)), } } // ------------------------------------------------------------------------- // Identification Handshake // ------------------------------------------------------------------------- async fn send_identification(&self) { let id = rand_u32(); let omikron_id = env::var("ID") .unwrap_or("0".to_string()) .parse::() .unwrap_or(0); let identify_msg = CommunicationValue::new(CommunicationType::identification) .with_id(id) .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); WAITING_TASKS.insert( id, WaitingTask { task: Box::new(|selfc, cv| { if cv.is_type(CommunicationType::error_not_found) { log_err!( 0, PrintType::Omega, "Identification failed: Omikron ID not found" ); return false; } if !cv.is_type(CommunicationType::challenge) { return false; } tokio::spawn(async move { if let Err(e) = selfc.handle_challenge(cv).await { log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); } }); true }), inserted_at: Instant::now(), }, ); self.send_message(&identify_msg).await; } async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { let challenge = cv .get_data(DataTypes::challenge) .as_str() .ok_or("Challenge not found")?; let server_pub_key = cv .get_data(DataTypes::public_key) .as_str() .ok_or("Public key not found")?; let decrypted_challenge = decrypt_b64( &secret_key_to_base64(&get_private_key()), server_pub_key, challenge, ) .map_err(|e| format!("Decryption failed: {:?}", e))?; let response_msg = CommunicationValue::new(CommunicationType::challenge_response) .with_id(cv.get_id()) .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); let response_id = response_msg.get_id(); WAITING_TASKS.insert( response_id, WaitingTask { task: Box::new(|selfc, final_cv| { if !final_cv.is_type(CommunicationType::identification_response) { log_err!(0, PrintType::Omega, "Expected identification_response"); return false; } let accepted = final_cv .get_data(DataTypes::accepted) .as_bool() .unwrap_or(false); if !accepted { log_err!(0, PrintType::Omega, "Omega did not accept identification"); return false; } tokio::spawn(async move { let mut state = selfc.state.write().await; if let ConnectionState::Connected { identified: _ } = *state { *state = ConnectionState::Connected { identified: true }; } drop(state); selfc.sync_client_iota_status().await; }); log!(0, PrintType::Omega, "Successfully identified with Omega"); true }), inserted_at: Instant::now(), }, ); self.send_message(&response_msg).await; Ok(()) } async fn sync_client_iota_status(self: Arc) { let mut connected_iota_ids: Vec = Vec::new(); let mut connected_user_ids: Vec = Vec::new(); let rho_connections_reader = RHO_CONNECTIONS.read().await; for iota_id in rho_connections_reader.keys() { connected_iota_ids.push(DataValue::Number(*iota_id)); } for rho in rho_connections_reader.values() { for client_conn in rho.get_client_connections().await { connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); } } drop(rho_connections_reader); let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) .add_data( DataTypes::rho_connections, DataValue::Number(connection_count().await as i64), ); self.send_message(&sync_msg).await; } // ------------------------------------------------------------------------- // Read Loop & Heartbeat // ------------------------------------------------------------------------- async fn read_loop( self: Arc, receiver: &mut Receiver, sender_handle: Arc, ) { // Monitor both receiver and sender handle for close let mut close_rx = sender_handle.subscribe_close(); loop { tokio::select! { result = receiver.receive() => { match result { Ok(cv) => { if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { log_cv_in!(PrintType::Omega, &cv); } if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { self.handle_pong(&cv).await; continue; } let msg_id = cv.get_id(); if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { if (task.task)(self.clone(), cv.clone()) { continue; } } if cv.is_type(CommunicationType::iota_user_data) { if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { let mut user_ids: Vec = Vec::new(); for value in users { if let DataValue::Number(user_id) = value { user_ids.push(*user_id as u64); } } let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { if let Some(rho) = connections.get(&iota_id) { rho.get_iota_connection().set_user_ids(user_ids).await; } } else { for rho in connections.values() { rho.get_iota_connection().set_user_ids(user_ids.clone()).await; } } } } } Err(e) => { log_err!(0, PrintType::Omega, "Receive error: {}", e); break; } } } _ = close_rx.changed() => { // Connection was closed by either side if let Some(reason) = close_rx.borrow().clone() { log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); } else { log_in!(0, PrintType::Omega, "Connection closed cleanly"); } break; } } } } async fn heartbeat_loop(self: Arc) { loop { sleep(HEARTBEAT_INTERVAL).await; // Check if still connected if !self.state.read().await.is_connected() { break; } // Check if sender is closed if let Some(sender) = self.sender.read().await.as_ref() { if sender.is_closed() { log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); break; } } else { break; } self.send_ping().await; } } async fn send_ping(&self) { let ping = CommunicationValue::new(CommunicationType::ping).add_data( DataTypes::send_time, DataValue::Number( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() as i64, ), ); self.send_message(&ping).await; } async fn handle_pong(&self, cv: &CommunicationValue) { let timestamp = cv .get_data(DataTypes::send_time) .as_number() .unwrap_or_else(|| { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs() as i64 }); *self.last_ping.lock().await = timestamp; } // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- pub async fn send_message(&self, cv: &CommunicationValue) { if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { log_cv_out!(PrintType::Omega, &cv); } let sender_guard = self.sender.read().await; if let Some(sender) = sender_guard.as_ref() { // Check if closed before sending if sender.is_closed() { log_err!(0, PrintType::Omega, "Cannot send: connection closed"); drop(sender_guard); // Trigger reconnection by closing the connection state if let Some(sender) = self.sender.write().await.take() { sender.close(); } return; } let sender_clone = Arc::clone(sender); drop(sender_guard); if let Err(e) = sender_clone.send(cv).await { log_err!(0, PrintType::Omega, "Send failed: {}", e); } } else { log_err!(0, PrintType::Omega, "Cannot send: not connected"); } } pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { if self.state.read().await.is_connected() { return Ok(()); } let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); let start = Instant::now(); loop { if self.state.read().await.is_connected() { return Ok(()); } if start.elapsed() >= timeout { return Err(format!( "Connection not established within {} seconds", timeout.as_secs() )); } sleep(Duration::from_millis(100)).await; } } pub async fn await_response( &self, cv: &CommunicationValue, timeout_duration: Option, ) -> Result { self.await_connection(timeout_duration).await?; let (tx, mut rx) = mpsc::channel(1); let msg_id = cv.get_id(); WAITING_TASKS.insert( msg_id, WaitingTask { task: Box::new(move |_, response_cv| { let inner_tx = tx.clone(); tokio::spawn(async move { let _ = inner_tx.send(response_cv).await; }); true }), inserted_at: Instant::now(), }, ); self.send_message(cv).await; let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); match tokio::time::timeout(timeout, rx.recv()).await { Ok(Some(response_cv)) => Ok(response_cv), Ok(_) => Err("Channel closed".to_string()), Err(_) => { WAITING_TASKS.remove(&msg_id); Err("Request timed out".to_string()) } } } #[allow(dead_code)] pub async fn is_connected(&self) -> bool { self.state.read().await.is_connected() } #[allow(dead_code)] pub async fn is_identified(&self) -> bool { self.state.read().await.is_identified() } #[allow(dead_code)] pub async fn close_iota(iota_id: i64) { let cv = CommunicationValue::new(CommunicationType::iota_disconnected) .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); OMEGA_CONNECTION.send_message(&cv).await; } pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { let msg_type = match state { UserStatus::iota_offline => CommunicationType::user_disconnected, UserStatus::user_offline => CommunicationType::user_disconnected, UserStatus::user_invisible => CommunicationType::user_disconnected, _ => CommunicationType::user_connected, }; let cv = CommunicationValue::new(msg_type) .add_data(DataTypes::user_id, DataValue::Number(user_id)) .add_data(DataTypes::user_state, DataValue::Str(state.to_string())); OMEGA_CONNECTION.send_message(&cv).await; } pub async fn user_states(user_id: i64, user_ids: Vec) { let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); let cv = CommunicationValue::new(CommunicationType::get_states) .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); let msg_id = cv.get_id(); WAITING_TASKS.insert( msg_id, WaitingTask { task: Box::new( move |_: Arc, response: CommunicationValue| { tokio::spawn(async move { let rho = rho_manager::get_rho_con_for_user(user_id).await; if let Some(rho) = rho { for client in rho.get_client_connections_for_user(user_id).await { client.send_message(&response).await; } } }); true }, ), inserted_at: Instant::now(), }, ); OMEGA_CONNECTION.send_message(&cv).await; } } // ============================================================================ // Global Instance // ============================================================================ static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { let conn = Arc::new(OmegaConnection::new()); // Start the connection manager immediately let conn_clone = conn.clone(); tokio::spawn(async move { conn_clone.start().await; }); start_task_cleanup_loop(); conn }); pub fn get_omega_connection() -> Arc { OMEGA_CONNECTION.clone() }