geph5-misc-rpc 0.2.92

Geph5 misc RPC protocols
Documentation
use std::{fmt, net::SocketAddr, str::FromStr};

use serde::{Deserialize, Serialize};

#[derive(Clone, Debug)]
pub enum TunnelCommand {
    Legacy { protocol: String, host: String },
    Rich(RichTunnelCommand),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RichTunnelCommand {
    pub protocol: String,
    pub host: String,
}

impl TunnelCommand {
    pub fn protocol(&self) -> &str {
        match self {
            Self::Legacy { protocol, .. } => protocol,
            Self::Rich(r) => &r.protocol,
        }
    }

    pub fn host(&self) -> &str {
        match self {
            Self::Legacy { host, .. } => host,
            Self::Rich(r) => &r.host,
        }
    }
}

impl fmt::Display for TunnelCommand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Legacy { protocol, host } => write!(f, "{protocol}${host}"),
            Self::Rich(r) => write!(f, "{}", serde_json::to_string(r).unwrap()),
        }
    }
}

impl FromStr for TunnelCommand {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.starts_with('{') {
            let rich: RichTunnelCommand = serde_json::from_str(s)
                .map_err(|e| anyhow::anyhow!("failed to parse rich tunnel command: {e}"))?;
            Ok(Self::Rich(rich))
        } else if let Some((protocol, host)) = s.split_once('$') {
            Ok(Self::Legacy {
                protocol: protocol.to_string(),
                host: host.to_string(),
            })
        } else {
            Ok(Self::Legacy {
                protocol: "tcp".to_string(),
                host: s.to_string(),
            })
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RichTunnelResponse {
    pub resolved_addr: SocketAddr,
    pub open_ms: Option<u32>,
}