porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use anyhow::{Context, Result};
use chrono::Utc;
use serde::{Deserialize, Serialize};

/// Command types sent from client to server
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Command {
    /// Register client ports with server
    RegisterPorts {
        client_id: String,
        ports: Vec<PortEntry>,
    },

    /// Remove all ports for a client
    UnregisterPorts {
        client_id: String,
    },

    /// Heartbeat keepalive
    Heartbeat {
        client_id: String,
        timestamp: i64,
    },
}

/// Port entry in a registration command
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortEntry {
    pub remote_port: u16,
    pub local_port: u16,
    pub protocol: String,
    pub description: String,
}

/// Response from server to client
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Response {
    /// Command succeeded
    Ok {
        message: String,
    },

    /// Command failed
    Error {
        message: String,
    },

    /// Heartbeat acknowledged
    HeartbeatAck {
        server_time: i64,
    },
}

impl Command {
    /// Serialize command to bytes
    pub fn to_bytes(&self) -> Result<Vec<u8>> {
        serde_json::to_vec(self).context("Failed to serialize command")
    }

    /// Deserialize command from bytes
    pub fn from_bytes(data: &[u8]) -> Result<Self> {
        serde_json::from_slice(data).context("Failed to deserialize command")
    }
}

impl Response {
    /// Serialize response to bytes
    pub fn to_bytes(&self) -> Result<Vec<u8>> {
        serde_json::to_vec(self).context("Failed to serialize response")
    }

    /// Deserialize response from bytes
    pub fn from_bytes(data: &[u8]) -> Result<Self> {
        serde_json::from_slice(data).context("Failed to deserialize response")
    }
}

/// Create a heartbeat command
pub fn heartbeat_command(client_id: &str) -> Command {
    Command::Heartbeat {
        client_id: client_id.to_string(),
        timestamp: Utc::now().timestamp(),
    }
}

/// Create a register ports command
pub fn register_ports_command(client_id: &str, ports: Vec<PortEntry>) -> Command {
    Command::RegisterPorts {
        client_id: client_id.to_string(),
        ports,
    }
}