porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use serde::{Deserialize, Serialize};
use std::fmt;

/// Role of this Porta instance
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    /// Machine behind CGNAT (no public IP)
    Client,
    /// Machine with public IP
    Server,
}

impl fmt::Display for Role {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Role::Client => write!(f, "client"),
            Role::Server => write!(f, "server"),
        }
    }
}

impl std::str::FromStr for Role {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "client" => Ok(Role::Client),
            "server" => Ok(Role::Server),
            _ => Err(anyhow::anyhow!("Invalid role: '{}'. Use 'client' or 'server'", s)),
        }
    }
}

/// Protocol type for port forwarding
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Protocol {
    Tcp,
    Udp,
}

impl fmt::Display for Protocol {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Protocol::Tcp => write!(f, "tcp"),
            Protocol::Udp => write!(f, "udp"),
        }
    }
}

impl std::str::FromStr for Protocol {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "tcp" => Ok(Protocol::Tcp),
            "udp" => Ok(Protocol::Udp),
            _ => Err(anyhow::anyhow!("Invalid protocol: '{}'. Use 'tcp' or 'udp'", s)),
        }
    }
}

/// A port forwarding mapping
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortMapping {
    /// Unique identifier for this mapping
    pub id: String,

    /// Local port to forward traffic to
    pub local_port: u16,

    /// Remote port to listen on (public side)
    pub remote_port: u16,

    /// Protocol (TCP or UDP)
    pub protocol: Protocol,

    /// Description of this mapping
    #[serde(default)]
    pub description: String,

    /// Whether this mapping is enabled
    #[serde(default = "default_true")]
    pub enabled: bool,
}

fn default_true() -> bool {
    true
}