use argh::FromArgs;
use super::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TlsCipher {
Chacha20Poly1305,
Aes128Gcm,
}
impl std::str::FromStr for TlsCipher {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"chacha20" | "chacha20poly1305" => Ok(TlsCipher::Chacha20Poly1305),
"aes128" | "aes128gcm" => Ok(TlsCipher::Aes128Gcm),
other => Err(format!(
"unknown cipher '{other}' (valid: chacha20, aes128)"
)),
}
}
}
#[derive(FromArgs, Clone)]
pub struct UserArgs {
#[argh(option, default = "8")]
pub download_threads: u32,
#[argh(option, default = "8")]
pub upload_threads: u32,
#[argh(switch, short = 'd')]
pub download_only: bool,
#[argh(switch, short = 'u')]
pub upload_only: bool,
#[argh(option, default = "100 * 1024 * 1024")]
pub bytes_to_download: usize,
#[argh(option, default = "50 * 1024 * 1024")]
pub bytes_to_upload: usize,
#[argh(option, default = "12")]
pub test_duration_seconds: u64,
#[argh(option)]
pub format: Option<String>,
#[argh(switch)]
pub no_header: bool,
#[argh(
option,
default = "String::from(\"https://speedtest.obscenegaming.net\")"
)]
pub server: String,
#[argh(option, default = "String::from(\"chacha20\")")]
pub cipher: String,
}
impl UserArgs {
pub fn validate(&mut self) -> Result<()> {
if self.download_only && self.upload_only {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Cannot specify both --download-only and --upload-only",
)));
}
if let Some(ref mut fmt) = self.format {
*fmt = fmt.to_ascii_lowercase();
if fmt != "json" && fmt != "csv" {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid format. Supported formats: json, csv",
)));
}
}
let trimmed = self.server.trim_end_matches('/');
if !trimmed.starts_with("https://") {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"--server must start with https:// (raw download path requires TLS)",
)));
}
self.server = trimmed.to_string();
if let Err(msg) = self.cipher.parse::<TlsCipher>() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
msg,
)));
}
Ok(())
}
pub fn cipher(&self) -> TlsCipher {
self.cipher.parse().expect("cipher validated")
}
}