use std::path::PathBuf;
use crate::connector::{ServiceConnector, ServiceInfo, ServiceLifecycle, ServiceStatus};
use super::helpers::{binary_on_path, detect_service, tcp_probe};
pub struct MpmConnector {
home_dir: Option<PathBuf>,
}
impl MpmConnector {
pub fn new() -> Self {
Self { home_dir: None }
}
#[cfg(test)]
pub fn with_home(home_dir: PathBuf) -> Self {
Self {
home_dir: Some(home_dir),
}
}
fn lock_file_path(&self) -> PathBuf {
let home = self
.home_dir
.clone()
.or_else(dirs::home_dir)
.unwrap_or_else(|| PathBuf::from("/tmp"));
home.join(".trusty-mpm").join("daemon.lock")
}
}
impl Default for MpmConnector {
fn default() -> Self {
Self::new()
}
}
fn parse_lock_addr(body: &str) -> Option<String> {
for line in body.lines() {
let Some((key, value)) = line.split_once('=') else {
continue;
};
if key.trim() != "addr" {
continue;
}
let unquoted = value.trim().trim_matches('"');
let host_port = unquoted
.strip_prefix("http://")
.or_else(|| unquoted.strip_prefix("https://"))
.unwrap_or(unquoted);
if !host_port.is_empty() {
return Some(host_port.to_string());
}
}
None
}
impl ServiceConnector for MpmConnector {
fn id(&self) -> &'static str {
"trusty-mpm"
}
fn display_name(&self) -> &'static str {
"Trusty MPM"
}
fn detect(&self) -> ServiceInfo {
if !binary_on_path("trusty-mpm") {
return ServiceInfo {
id: self.id().to_string(),
display_name: self.display_name().to_string(),
status: ServiceStatus::Absent,
version: None,
url: None,
hint: None,
lifecycle: ServiceLifecycle::Daemon,
};
}
if let Ok(dir) = trusty_common::resolve_data_dir("trusty-mpm") {
let addr_file = dir.join("http_addr");
if addr_file.exists() {
return detect_service(self.id(), self.display_name(), "trusty-mpm", addr_file);
}
}
if let Ok(body) = std::fs::read_to_string(self.lock_file_path())
&& let Some(addr) = parse_lock_addr(&body)
&& tcp_probe(&addr)
{
return ServiceInfo {
id: self.id().to_string(),
display_name: self.display_name().to_string(),
status: ServiceStatus::Running,
version: None,
url: None,
hint: Some(
"daemon is running but pre-dates #1849 — restart to enable proxy".to_string(),
),
lifecycle: ServiceLifecycle::Daemon,
};
}
ServiceInfo {
id: self.id().to_string(),
display_name: self.display_name().to_string(),
status: ServiceStatus::Available,
version: None,
url: None,
hint: None,
lifecycle: ServiceLifecycle::Daemon,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::net::TcpListener;
use tempfile::TempDir;
use super::super::ENV_LOCK;
use trusty_common::DATA_DIR_OVERRIDE_ENV;
#[test]
fn parse_lock_addr_strips_scheme() {
let body = "pid = 42\naddr = \"http://127.0.0.1:7880\"\nstarted_at = \"x\"\n";
assert_eq!(parse_lock_addr(body).as_deref(), Some("127.0.0.1:7880"));
}
#[test]
fn parse_lock_addr_none_when_absent() {
assert_eq!(parse_lock_addr("pid = 42\n"), None);
}
#[test]
fn parse_lock_addr_well_formed_no_scheme() {
assert_eq!(
parse_lock_addr("addr = \"127.0.0.1:9001\"\n").as_deref(),
Some("127.0.0.1:9001")
);
}
#[test]
fn parse_lock_addr_ignores_prefixed_key() {
assert_eq!(
parse_lock_addr("addr_extra = \"http://6.6.6.6:6666\"\n"),
None
);
}
#[test]
fn parse_lock_addr_prefers_exact_key_over_decoy() {
let body = "addr_extra = \"http://6.6.6.6:6666\"\naddr = \"http://127.0.0.1:7880\"\n";
assert_eq!(parse_lock_addr(body).as_deref(), Some("127.0.0.1:7880"));
}
#[test]
fn mpm_connector_absent_binary() {
if which::which("trusty-mpm").is_ok() {
return;
}
let tmp = TempDir::new().expect("tempdir");
let info = MpmConnector::with_home(tmp.path().to_path_buf()).detect();
assert_eq!(info.status, ServiceStatus::Absent);
assert_eq!(info.id, "trusty-mpm");
}
#[test]
fn mpm_connector_parses_lock_addr() {
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let tmp = TempDir::new().expect("tempdir");
let data_tmp = TempDir::new().expect("data-tempdir");
unsafe {
std::env::set_var(DATA_DIR_OVERRIDE_ENV, data_tmp.path());
}
let lock = tmp.path().join(".trusty-mpm").join("daemon.lock");
fs::create_dir_all(lock.parent().expect("parent")).expect("mkdir");
fs::write(&lock, "pid = 1\naddr = \"http://127.0.0.1:14998\"\n").expect("write");
let info = MpmConnector::with_home(tmp.path().to_path_buf()).detect();
unsafe {
std::env::remove_var(DATA_DIR_OVERRIDE_ENV);
}
if which::which("trusty-mpm").is_ok() {
assert_eq!(info.status, ServiceStatus::Available);
} else {
assert_eq!(info.status, ServiceStatus::Absent);
}
}
#[test]
fn mpm_connector_no_lock_file() {
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let tmp = TempDir::new().expect("tempdir");
let data_tmp = TempDir::new().expect("data-tempdir");
unsafe {
std::env::set_var(DATA_DIR_OVERRIDE_ENV, data_tmp.path());
}
let info = MpmConnector::with_home(tmp.path().to_path_buf()).detect();
unsafe {
std::env::remove_var(DATA_DIR_OVERRIDE_ENV);
}
if which::which("trusty-mpm").is_ok() {
assert_eq!(info.status, ServiceStatus::Available);
} else {
assert_eq!(info.status, ServiceStatus::Absent);
}
}
#[test]
fn mpm_connector_surfaces_url_via_http_addr() {
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let data_tmp = TempDir::new().expect("data-tempdir");
unsafe {
std::env::set_var(DATA_DIR_OVERRIDE_ENV, data_tmp.path());
}
let mpm_dir = data_tmp.path().join("trusty-mpm");
fs::create_dir_all(&mpm_dir).expect("mkdir");
let listener = TcpListener::bind("127.0.0.1:0").expect("bind free port");
let addr = listener.local_addr().expect("local_addr").to_string();
fs::write(mpm_dir.join("http_addr"), &addr).expect("write addr");
let info = MpmConnector::new().detect();
drop(listener);
unsafe {
std::env::remove_var(DATA_DIR_OVERRIDE_ENV);
}
if which::which("trusty-mpm").is_ok() {
assert_eq!(
info.status,
ServiceStatus::Running,
"http_addr present + port open must yield Running, got: {info:?}"
);
assert_eq!(
info.url,
Some(format!("http://{addr}")),
"Running status must include daemon base URL for proxy routing"
);
} else {
assert_eq!(info.status, ServiceStatus::Absent);
}
}
}