anttp 0.26.0

AntTP is an HTTP server for the Autonomi Network
use std::env;
use std::net::SocketAddr;
use ant_core::data::EvmNetwork::ArbitrumOne;
use log::info;
use clap::Parser;
use crate::error::CreateError;
use saorsa_pqc::api::sig::{MlDsaSecretKey, MlDsaVariant};
use saorsa_pqc::ml_dsa_65;

#[derive(Clone, Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct AntTpConfig {
    #[arg(short, long, default_value = "0.0.0.0:18888")]
    pub listen_address: SocketAddr,

    #[arg(long, default_value = "0.0.0.0:18889")]
    pub https_listen_address: SocketAddr,

    #[arg(long, default_value = "0.0.0.0:18887")]
    pub grpc_listen_address: SocketAddr,

    #[arg(short, long, default_value = "")]
    pub static_file_directory: String,

    #[arg(short, long, default_value = "")]
    pub wallet_private_key: String,

    #[arg(short, long, default_value_t = 8)]
    pub download_threads: usize,

    #[arg(short, long, default_value = "")]
    pub app_private_key: String,

    #[arg(short, long, default_value = "55dcbc4624699d219b8ec293339a3b81e68815397f5a502026784d8122d09fce")]
    pub resolver_private_key: String,

    #[arg(short, long, default_value = "43533a7c56b27cb44ef78d84458dd8494ead68ac71f53781ff166fa1a1b31a64")]
    pub bookmarks_address: String,

    #[arg(short, long, default_value_t = false)]
    pub uploads_disabled: bool,

    #[arg(long, default_value_t = false)]
    pub mcp_tools_disabled: bool,

    #[arg(long, default_value_t = false)]
    pub grpc_disabled: bool,

    #[arg(short, long, default_value_t = 5)]
    pub cached_mutable_ttl: u64,

    #[arg(short, long, value_delimiter = ',')]
    pub peers: Vec<SocketAddr>,

    #[arg(short, long, default_value_t = AntTpConfig::get_default_map_cache_directory())]
    pub map_cache_directory: String,

    #[arg(short, long, default_value_t = AntTpConfig::get_default_evm_network())]
    pub evm_network: String,

    #[arg(long, default_value_t = 1024)]
    pub immutable_disk_cache_size: usize,

    #[arg(long, default_value_t = 32)]
    pub immutable_memory_cache_size: usize,

    #[arg(short, long, default_value_t = 30)]
    pub idle_disconnect: u64,

    #[arg(long, default_value_t = 128)]
    pub command_buffer_size: usize,

    #[arg(long, default_value = "")]
    pub access_list_address: String,
}

impl AntTpConfig {

    pub fn read_args() -> AntTpConfig {
        let ant_tp_config = AntTpConfig::parse();
        info!("Listen address: [{}]", ant_tp_config.listen_address);
        info!("Static file directory: [{}]", ant_tp_config.static_file_directory);
        info!("Wallet private key: [*****]");
        info!("Download threads: [{}]", ant_tp_config.download_threads);
        info!("Uploads disabled: [{}]", ant_tp_config.uploads_disabled);
        info!("MCP tools disabled: [{}]", ant_tp_config.mcp_tools_disabled);
        info!("gRPC disabled: [{}]", ant_tp_config.grpc_disabled);
        if ant_tp_config.app_private_key.is_empty() {
            let dsa = ml_dsa_65();
            let (_, private_key) = dsa.generate_keypair().unwrap();
            info!("No app/personal private key provided. Try this one: [{:?}]", hex::encode(private_key.to_bytes()));
        } else {
            info!("App/personal private key: [*****]");
        }
        info!("Bookmarks address: {:?}", ant_tp_config.bookmarks_address);
        info!("Cached mutable TTL: {:?}", ant_tp_config.cached_mutable_ttl);
        //info!("Peers: {:?}", ant_tp_config.peers);
        info!("Map cache directory: {:?}", ant_tp_config.map_cache_directory);
        info!("EVM network: {:?}", ant_tp_config.evm_network);
        info!("Immutable disk cache size (MB): {:?}", ant_tp_config.immutable_disk_cache_size);
        info!("Immutable memory cache size (slots): {:?}", ant_tp_config.immutable_memory_cache_size);
        info!("Idle disconnect from Autonomi (seconds): {:?}", ant_tp_config.idle_disconnect);
        info!("Command buffer size (slots): {:?}", ant_tp_config.command_buffer_size);
        info!("Access list archive: {:?}", ant_tp_config.access_list_address);
        info!("Resolver private key: {:?}", ant_tp_config.resolver_private_key);
        ant_tp_config
    }

    pub fn get_default_map_cache_directory() -> String {
        env::temp_dir().to_str().unwrap().to_owned() + "/anttp/cache/"
    }

    pub fn get_default_evm_network() -> String {
        ArbitrumOne.to_string()
    }

    pub fn get_app_private_key(&self) -> Result<MlDsaSecretKey, CreateError> {
        match MlDsaSecretKey::from_bytes(
            MlDsaVariant::MlDsa87,
            hex::decode(self.app_private_key.clone().as_str()).unwrap_or(vec![]).as_slice())
        {
            Ok(app_secret_key) => Ok(app_secret_key),
            Err(e) => Err(CreateError::DataKeyMissing(e.to_string()))
        }
    }

    pub fn get_resolver_private_key(&self) -> Result<MlDsaSecretKey, CreateError> {
        match MlDsaSecretKey::from_bytes(
            MlDsaVariant::MlDsa87,
            hex::decode(self.resolver_private_key.clone().as_str()).unwrap_or(vec![]).as_slice())
        {
            Ok(resolver_secret_key) => Ok(resolver_secret_key),
            Err(e) => Err(CreateError::DataKeyMissing(e.to_string()))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_anttp_config_mcp_tools_disabled_default() {
        // We can't easily test Parser::parse() without changing args, 
        // but we can test the structure's default behavior if we had a way to construct it.
        // Since we are using clap Parser, let's test with try_parse_from
        
        let config = AntTpConfig::try_parse_from(&["anttp"]).unwrap();
        assert!(!config.mcp_tools_disabled);
    }

    #[test]
    fn test_anttp_config_mcp_tools_disabled_long_arg() {
        let config = AntTpConfig::try_parse_from(&["anttp", "--mcp-tools-disabled"]).unwrap();
        assert!(config.mcp_tools_disabled);
    }

    #[test]
    fn test_anttp_config_grpc_disabled_default() {
        let config = AntTpConfig::try_parse_from(&["anttp"]).unwrap();
        assert!(!config.grpc_disabled);
    }

    #[test]
    fn test_anttp_config_grpc_disabled_long_arg() {
        let config = AntTpConfig::try_parse_from(&["anttp", "--grpc-disabled"]).unwrap();
        assert!(config.grpc_disabled);
    }
}