porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use std::collections::HashMap;
use parking_lot::RwLock;

/// Simple client registry for tracking connected clients
pub struct ClientRegistry {
    clients: RwLock<HashMap<String, ClientInfo>>,
}

/// Information about a connected client
#[derive(Debug, Clone)]
pub struct ClientInfo {
    pub client_id: String,
    pub connected_at: i64,
    pub last_heartbeat: i64,
}

impl ClientRegistry {
    pub fn new() -> Self {
        Self {
            clients: RwLock::new(HashMap::new()),
        }
    }

    /// Register a client
    pub fn register(&self, client_id: &str) {
        let mut clients = self.clients.write();
        clients.insert(
            client_id.to_string(),
            ClientInfo {
                client_id: client_id.to_string(),
                connected_at: chrono::Utc::now().timestamp(),
                last_heartbeat: chrono::Utc::now().timestamp(),
            },
        );
        tracing::info!("Client registered: {}", client_id);
    }

    /// Update heartbeat timestamp
    pub fn heartbeat(&self, client_id: &str) {
        let mut clients = self.clients.write();
        if let Some(info) = clients.get_mut(client_id) {
            info.last_heartbeat = chrono::Utc::now().timestamp();
        }
    }

    /// Unregister a client
    pub fn unregister(&self, client_id: &str) {
        let mut clients = self.clients.write();
        clients.remove(client_id);
        tracing::info!("Client unregistered: {}", client_id);
    }

    /// Check if a client is registered
    pub fn is_registered(&self, client_id: &str) -> bool {
        self.clients.read().contains_key(client_id)
    }

    /// Get all registered clients
    pub fn get_all(&self) -> Vec<ClientInfo> {
        self.clients.read().values().cloned().collect()
    }
}

impl Default for ClientRegistry {
    fn default() -> Self {
        Self::new()
    }
}