porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use anyhow::{Context, Result};
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

use crate::config::ClientConfig;
use super::protocol::{Command, Response};

/// Control client - sends commands to server via TCP over WireGuard tunnel
pub struct ControlClient {
    server_addr: SocketAddr,
    client_id: String,
    hmac_secret: String,
}

impl ControlClient {
    /// Create a new control client
    pub fn new(config: &ClientConfig) -> Result<Self> {
        let server_addr: SocketAddr = format!(
            "{}:{}",
            config.server_address, config.server_port
        )
        .parse()
        .context("Invalid server address")?;

        Ok(Self {
            server_addr,
            client_id: config.client_id.clone(),
            hmac_secret: config.hmac_secret.clone(),
        })
    }

    /// Send a command to the server and wait for response
    pub async fn send_command(&self, command: &Command) -> Result<Response> {
        let mut stream = TcpStream::connect(&self.server_addr)
            .await
            .context(format!("Failed to connect to server at {}", self.server_addr))?;

        // Send command
        let data = command.to_bytes()?;
        let len = data.len() as u32;
        stream.write_all(&len.to_le_bytes()).await?;
        stream.write_all(&data).await?;

        // Read response
        let mut len_buf = [0u8; 4];
        stream.read_exact(&mut len_buf).await?;
        let resp_len = u32::from_le_bytes(len_buf) as usize;

        let mut resp_buf = vec![0u8; resp_len];
        stream.read_exact(&mut resp_buf).await?;

        let response = Response::from_bytes(&resp_buf)?;
        Ok(response)
    }

    /// Register ports with the server
    pub async fn register_ports(&self, ports: &[crate::control::protocol::PortEntry]) -> Result<Response> {
        let command = Command::RegisterPorts {
            client_id: self.client_id.clone(),
            ports: ports.to_vec(),
        };
        self.send_command(&command).await
    }

    /// Send heartbeat
    pub async fn heartbeat(&self) -> Result<Response> {
        let command = Command::Heartbeat {
            client_id: self.client_id.clone(),
            timestamp: chrono::Utc::now().timestamp(),
        };
        self.send_command(&command).await
    }
}