use crate::HelperServiceClient;
use crate::error::HelperError;
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("helper not reachable: {0}")]
Connection(#[from] std::io::Error),
#[error("helper rpc failed: {0}")]
Rpc(#[from] tarpc::client::RpcError),
#[error("unrecognized helper version {0:?}")]
UnrecognizedVersion(String),
#[error("helper {installed} is incompatible; required {required}")]
IncompatibleVersion {
installed: String,
required: &'static str,
},
#[error(transparent)]
Helper(#[from] HelperError),
}
pub struct Client {
inner: HelperServiceClient,
}
impl Client {
pub async fn connect() -> Result<Self, ClientError> {
let inner = crate::connect().await?;
let client = Self { inner };
client.ensure_compatible().await?;
Ok(client)
}
pub async fn connect_to(path: &str) -> Result<Self, ClientError> {
let transport = tarpc::serde_transport::unix::connect(
path,
tarpc::tokio_serde::formats::Bincode::default,
)
.await?;
let inner =
crate::HelperServiceClient::new(tarpc::client::Config::default(), transport).spawn();
let client = Self { inner };
client.ensure_compatible().await?;
Ok(client)
}
pub async fn probe_version() -> Result<String, ClientError> {
let inner = crate::connect().await?;
Self { inner }.version().await
}
pub async fn route_add(&self, subnet: &str, iface: &str) -> Result<(), ClientError> {
Ok(self
.inner
.route_add(tarpc::context::current(), subnet.into(), iface.into())
.await??)
}
pub async fn route_remove(&self, subnet: &str) -> Result<(), ClientError> {
Ok(self
.inner
.route_remove(tarpc::context::current(), subnet.into())
.await??)
}
pub async fn dns_install(&self, domain: &str, port: u16) -> Result<(), ClientError> {
Ok(self
.inner
.dns_install(tarpc::context::current(), domain.into(), port)
.await??)
}
pub async fn dns_uninstall(&self, domain: &str) -> Result<(), ClientError> {
Ok(self
.inner
.dns_uninstall(tarpc::context::current(), domain.into())
.await??)
}
pub async fn dns_status(&self, domain: &str) -> Result<bool, ClientError> {
Ok(self
.inner
.dns_status(tarpc::context::current(), domain.into())
.await??)
}
pub async fn hosts_alias_install(&self) -> Result<(), ClientError> {
Ok(self
.inner
.hosts_alias_install(tarpc::context::current())
.await??)
}
pub async fn hosts_alias_uninstall(&self) -> Result<(), ClientError> {
Ok(self
.inner
.hosts_alias_uninstall(tarpc::context::current())
.await??)
}
pub async fn hosts_alias_status(&self) -> Result<bool, ClientError> {
Ok(self
.inner
.hosts_alias_status(tarpc::context::current())
.await??)
}
pub async fn socket_link(&self, target: &str) -> Result<(), ClientError> {
Ok(self
.inner
.socket_link(tarpc::context::current(), target.into())
.await??)
}
pub async fn socket_unlink(&self) -> Result<(), ClientError> {
Ok(self
.inner
.socket_unlink(tarpc::context::current())
.await??)
}
pub async fn cli_link(&self, name: &str, target: &str) -> Result<(), ClientError> {
Ok(self
.inner
.cli_link(tarpc::context::current(), name.into(), target.into())
.await??)
}
pub async fn cli_unlink(&self, name: &str) -> Result<(), ClientError> {
Ok(self
.inner
.cli_unlink(tarpc::context::current(), name.into())
.await??)
}
pub async fn version(&self) -> Result<String, ClientError> {
Ok(self.inner.version(tarpc::context::current()).await?)
}
async fn ensure_compatible(&self) -> Result<(), ClientError> {
let version = self.version().await?;
let installed = arcbox_constants::helper::parse_helper_version(&version)
.ok_or_else(|| ClientError::UnrecognizedVersion(version.clone()))?;
let required = arcbox_constants::helper::MIN_HELPER_VERSION;
let minimum = arcbox_constants::helper::parse_semver_triple(required)
.expect("MIN_HELPER_VERSION is covered by a unit test");
if arcbox_constants::helper::helper_version_satisfies(installed, minimum) {
Ok(())
} else {
Err(ClientError::IncompatibleVersion {
installed: version,
required,
})
}
}
}