use std::path::Path;
use anyhow::{Context as _, anyhow};
use fs_mistrust::{Mistrust, anon_home::PathExt as _};
use serde::{Serialize, Serializer};
use tor_general_addr::general;
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
pub(crate) struct PortInfo {
pub(crate) ports: Vec<Port>,
}
impl PortInfo {
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
pub(crate) fn write_to_file(&self, mistrust: &Mistrust, path: &Path) -> anyhow::Result<()> {
let s = serde_json::to_string(self)?;
let (Some(parent), Some(file_name)) = (path.parent(), path.file_name()) else {
return Err(anyhow!(
"port_info_file {} is not something we can write to",
path.anonymize_home()
));
};
let parent = if parent.to_str() == Some("") {
Path::new(".")
} else {
parent
};
let dir = mistrust
.verifier()
.permit_readable()
.make_secure_dir(parent)
.with_context(|| {
format!(
"Creating parent directory for port_info_file {}",
path.anonymize_home()
)
})?;
dir.write_and_replace(file_name, s)
.with_context(|| format!("Unable to write port_info_file {}", path.anonymize_home()))?;
Ok(())
}
}
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
pub(crate) struct Port {
pub(crate) protocol: SupportedProtocol,
#[serde(serialize_with = "serialize_address")]
pub(crate) address: general::SocketAddr,
}
fn serialize_address<S: Serializer>(addr: &general::SocketAddr, ser: S) -> Result<S::Ok, S::Error> {
match addr.try_to_string() {
Some(string) => ser.serialize_str(&string),
None => ser.serialize_none(),
}
}
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
#[allow(unused)] #[non_exhaustive]
pub(crate) enum SupportedProtocol {
#[serde(rename = "socks")]
Socks,
#[serde(rename = "http")]
Http,
#[serde(rename = "dns_udp")]
DnsUdp,
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use std::str::FromStr;
use super::*;
#[test]
fn format() {
use SupportedProtocol::*;
let pi = PortInfo {
ports: vec![Port {
protocol: Socks,
address: "127.0.0.1:99".parse().unwrap(),
}],
};
let got = serde_json::to_string(&pi).unwrap();
let expected = r#"
{ "ports" : [ {"protocol":"socks", "address":"inet:127.0.0.1:99"} ] }
"#;
assert_eq!(
serde_json::Value::from_str(&got).unwrap(),
serde_json::Value::from_str(expected).unwrap()
);
}
}