    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114

    use std::path::PathBuf;

    use clap::{Parser, ValueEnum};
    use csflow::{Connector, memflow::Inventory};

    const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535;
    const POLL_RANGE: std::ops::RangeInclusive<usize> = 1..=1000;

    #[derive(Parser)]
    #[command(author, version = version(), about, long_about = None)]
    pub struct Cli {
        /// Specifies the connector type for DMA
        #[clap(value_enum, short, long, ignore_case = true, default_value_t = Connector::Qemu)]
        pub connector: Connector,

        /// Name of the Pcileech device
        #[clap(long, default_value_t = String::from("FPGA"))]
        pub pcileech_device: String,

        /// Port number for the Webserver to run on
        #[arg(short, long, default_value_t = 8000, value_parser = port_in_range)]
        pub port: u16,

        /// Path to the directory served by the Webserver
        #[arg(short, long, default_value = "./webradar", value_parser = valid_path)]
        pub web_path: PathBuf,

        /// Polling frequency in times per second for the DMA thread
        #[arg(short = 'r', long, default_value_t = 60, value_parser = poll_in_range)]
        pub poll_rate: u16,

        /// Verbosity level for logging to the console
        #[arg(value_enum, long, short,  ignore_case = true, default_value_t = Loglevel::Warn)]
        pub loglevel: Loglevel,
    }

    fn version() -> String {
        let pkg_ver = env!("CARGO_PKG_VERSION");
        let git_hash = option_env!("VERGEN_GIT_SHA").unwrap_or("unknown");
        let commit_date = option_env!("VERGEN_GIT_COMMIT_DATE").unwrap_or("unknown");
        let avail_cons = {
            let inventory = Inventory::scan();
            inventory.available_connectors().join(", ")
        };

        format!(" {pkg_ver} (rev {git_hash})\nCommit Date: {commit_date}\nAvailable Connectors: {avail_cons}")
    }

    fn port_in_range(s: &str) -> Result<u16, String> {
        let port: usize = s
            .parse()
            .map_err(|_| format!("`{s}` isn't a port number"))?;
        if PORT_RANGE.contains(&port) {
            Ok(port as u16)
        } else {
            Err(format!(
                "port not in range {}-{}",
                PORT_RANGE.start(),
                PORT_RANGE.end()
            ))
        }
    }

    fn valid_path(s: &str) -> Result<PathBuf, String> {
        let path = PathBuf::from(s);

        if !path.exists() {
            return Err("Path does not exist".to_string())
        }

        if !path.is_dir() {
            return Err("Path is not a directory".to_string())
        }

        Ok(path)
    }

    fn poll_in_range(s: &str) -> Result<u16, String> {
        let port: usize = s
            .parse()
            .map_err(|_| format!("`{s}` isn't a valid number"))?;
        if POLL_RANGE.contains(&port) {
            Ok(port as u16)
        } else {
            Err(format!(
                "not in range {}-{}",
                POLL_RANGE.start(),
                POLL_RANGE.end()
            ))
        }
    }

    /// Wrapper because log::LevelFilter doesn't implement ValueEnum
    #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)]
    pub enum Loglevel {
        Error,
        #[default]
        Warn,
        Info,
        Debug,
        Trace,
    }

    impl From<Loglevel> for log::LevelFilter {
        fn from(val: Loglevel) -> Self {
            match val {
                Loglevel::Error => log::LevelFilter::Error,
                Loglevel::Warn => log::LevelFilter::Warn,
                Loglevel::Info => log::LevelFilter::Info,
                Loglevel::Debug => log::LevelFilter::Debug,
                Loglevel::Trace => log::LevelFilter::Trace,
            }
        }
    }