diff options
Diffstat (limited to 'src/model.rs')
| -rw-r--r-- | src/model.rs | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..8966956 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,147 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Trojan { + pub host: String, + pub port: u16, + pub password: String, + pub server_name: String, + pub alpn: Vec<String>, + pub fingerprint: Option<String>, +} + +impl Trojan { + pub fn identity(&self) -> (&str, u16, &str) { + (&self.host, self.port, &self.password) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Entry { + pub id: String, + pub name: String, + pub status: String, + pub explanation: Option<String>, + pub connection: Option<Trojan>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ConfigurationSummary { + pub id: String, + pub subscription_id: String, + pub name: String, + pub host: Option<String>, + pub port: Option<u16>, + pub status: String, + pub explanation: Option<String>, + pub selected: bool, + pub ping_ms: Option<u64>, + pub speed_bytes_per_second: Option<f64>, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Subscription { + pub id: String, + pub name: String, + pub url: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SubscriptionSummary { + pub id: String, + pub name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct Settings { + pub ping_url: String, + pub speed_url: String, + pub maximum_download_mib: u64, + pub maximum_duration_seconds: u64, +} + +impl Default for Settings { + fn default() -> Self { + Self { + ping_url: "https://speed.cloudflare.com/__down?bytes=0".into(), + speed_url: "https://speed.cloudflare.com/__down?bytes=10485760".into(), + maximum_download_mib: 10, + maximum_duration_seconds: 10, + } + } +} + +impl Settings { + pub fn validate(&self) -> anyhow::Result<()> { + for value in [&self.ping_url, &self.speed_url] { + let url = url::Url::parse(value) + .map_err(|_| anyhow::anyhow!("Use an absolute HTTPS diagnostic URL"))?; + anyhow::ensure!( + url.scheme() == "https" + && url.host_str().is_some() + && url.username().is_empty() + && url.password().is_none() + && url.fragment().is_none(), + "Diagnostic URLs require HTTPS without credentials or fragments" + ); + } + anyhow::ensure!( + self.maximum_download_mib > 0 + && self.maximum_download_mib.checked_mul(1_048_576).is_some(), + "Download limit must be a positive representable MiB count" + ); + anyhow::ensure!( + self.maximum_duration_seconds > 0, + "Duration limit must be positive" + ); + anyhow::ensure!( + std::time::Instant::now() + .checked_add(std::time::Duration::from_secs( + self.maximum_duration_seconds + )) + .is_some(), + "Duration exceeds the supported clock range" + ); + Ok(()) + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Definitions { + pub version: u32, + #[serde(default)] + pub settings: Settings, + #[serde(default)] + pub subscriptions: Vec<Subscription>, +} + +impl Default for Definitions { + fn default() -> Self { + Self { + version: 1, + settings: Settings::default(), + subscriptions: vec![], + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Status { + pub version: u32, + pub requested_enabled: bool, + pub selected_configuration: Option<String>, + pub generation: u64, + pub routing: String, + pub health: String, + pub last_error: Option<String>, +} + +pub fn display_text(value: &str) -> String { + value + .chars() + .filter(|c| !c.is_control()) + .take(256) + .collect() +} |