use serde::{Deserialize, Serialize};
use std::sync::RwLock;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct DataExfilDetectorOptions {
pub enabled: bool,
pub trusted_destinations: Vec<String>,
}
impl Default for DataExfilDetectorOptions {
fn default() -> Self {
Self {
enabled: true,
trusted_destinations: Vec::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct DetectorOptions {
pub data_exfil: DataExfilDetectorOptions,
}
static RUNTIME: RwLock<Option<DetectorOptions>> = RwLock::new(None);
pub fn install(opts: DetectorOptions) -> bool {
let mut guard = RUNTIME.write().expect("detector options RwLock poisoned");
if guard.is_some() {
return false;
}
*guard = Some(opts);
true
}
pub fn reinstall(opts: DetectorOptions) {
*RUNTIME.write().expect("detector options RwLock poisoned") = Some(opts);
}
pub fn current() -> DetectorOptions {
RUNTIME
.read()
.expect("detector options RwLock poisoned")
.clone()
.unwrap_or_default()
}
#[doc(hidden)]
pub fn _reset_for_tests() {
*RUNTIME.write().expect("detector options RwLock poisoned") = None;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_documented() {
let o = DetectorOptions::default();
assert!(o.data_exfil.enabled);
assert!(o.data_exfil.trusted_destinations.is_empty());
}
#[test]
fn toml_roundtrip() {
let opts = DetectorOptions {
data_exfil: DataExfilDetectorOptions {
enabled: false,
trusted_destinations: vec![
"https://api.internal/".into(),
"https://telemetry.".into(),
],
},
};
let s = toml::to_string(&opts).unwrap();
let back: DetectorOptions = toml::from_str(&s).unwrap();
assert_eq!(opts, back);
}
#[test]
fn missing_section_uses_defaults() {
let toml_str = r#"# empty"#;
let cfg: DetectorOptions = toml::from_str(toml_str).unwrap();
assert!(cfg.data_exfil.enabled);
}
}