porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use anyhow::{Context, Result};
use std::sync::Arc;
use tokio::sync::Notify;

use crate::config::{Config, Role};
use super::interface::InterfaceManager;

/// WireGuard tunnel manager
pub struct Tunnel {
    interface: InterfaceManager,
    role: Role,
    config: Config,
    shutdown: Arc<Notify>,
}

impl Tunnel {
    /// Create a new tunnel from config
    pub fn new(config: Config) -> Result<Self> {
        let interface_name = config.interface().to_string();
        let role = config.role();

        Ok(Self {
            interface: InterfaceManager::new(&interface_name),
            role,
            config,
            shutdown: Arc::new(Notify::new()),
        })
    }

    /// Start the WireGuard tunnel
    pub async fn start(&self) -> Result<()> {
        tracing::info!("Starting WireGuard tunnel as {:?}", self.role);

        match self.role {
            Role::Client => self.start_client().await,
            Role::Server => self.start_server().await,
        }
    }

    /// Start as client (connects to server)
    async fn start_client(&self) -> Result<()> {
        if let Config::Client(config) = &self.config {
            // Create and configure interface
            self.interface.create(
                Role::Client,
                &config.local_ip,
                0, // Client doesn't listen
            )?;

            // Set private key
            let key_path = crate::config::client_key_path();
            let private_key = std::fs::read_to_string(&key_path)
                .context(format!("Failed to read private key from {}", key_path.display()))?;
            self.interface.set_private_key(private_key.trim())?;

            // Add server as peer
            let endpoint = format!("{}:{}", config.server_address, config.server_port);
            self.interface.add_peer(
                &config.server_ip.split('/').next().unwrap_or(""),
                Some(&endpoint),
                &["0.0.0.0/0", "::/0"],
            )?;

            tracing::info!(
                "Client tunnel started, connecting to {}:{}",
                config.server_address,
                config.server_port
            );
        }

        Ok(())
    }

    /// Start as server (listens for clients)
    async fn start_server(&self) -> Result<()> {
        if let Config::Server(config) = &self.config {
            // Create and configure interface
            self.interface.create(
                Role::Server,
                &config.ip,
                config.listen_port,
            )?;

            // Set private key
            let key_path = crate::config::server_key_path();
            let private_key = std::fs::read_to_string(&key_path)
                .context(format!("Failed to read private key from {}", key_path.display()))?;
            self.interface.set_private_key(private_key.trim())?;

            // Add allowed clients
            for client in &config.allowed_clients {
                self.interface.add_peer(
                    &client.pubkey,
                    None, // Client will set its own endpoint
                    &["10.0.0.2/32"], // Allow traffic to this client
                )?;

                tracing::info!("Added client peer: {} ({})", client.id, client.pubkey);
            }

            tracing::info!("Server tunnel started on port {}", config.listen_port);
        }

        Ok(())
    }

    /// Stop the tunnel
    pub async fn stop(&self) -> Result<()> {
        tracing::info!("Stopping WireGuard tunnel");

        self.interface.bring_down()?;
        self.interface.delete()?;

        tracing::info!("WireGuard tunnel stopped");
        Ok(())
    }

    /// Check if tunnel is active
    pub fn is_active(&self) -> bool {
        self.interface.is_up()
    }

    /// Get tunnel statistics
    pub fn stats(&self) -> TunnelStats {
        // Read stats from /proc/net/wg or wg show
        // For now, return basic info
        TunnelStats {
            is_active: self.interface.is_up(),
            bytes_sent: 0,
            bytes_received: 0,
            packets_sent: 0,
            packets_received: 0,
        }
    }
}

/// Tunnel statistics
#[derive(Debug, Clone)]
pub struct TunnelStats {
    pub is_active: bool,
    pub bytes_sent: u64,
    pub bytes_received: u64,
    pub packets_sent: u64,
    pub packets_received: u64,
}