1use crate::HelperServiceClient;
8use crate::error::HelperError;
9
10#[derive(Debug, thiserror::Error)]
12pub enum ClientError {
13 #[error("helper not reachable: {0}")]
15 Connection(#[from] std::io::Error),
16 #[error("helper rpc failed: {0}")]
18 Rpc(#[from] tarpc::client::RpcError),
19 #[error("unrecognized helper version {0:?}")]
21 UnrecognizedVersion(String),
22 #[error("helper {installed} is incompatible; required {required}")]
24 IncompatibleVersion {
25 installed: String,
27 required: &'static str,
29 },
30 #[error(transparent)]
32 Helper(#[from] HelperError),
33}
34
35pub struct Client {
37 inner: HelperServiceClient,
38}
39
40impl Client {
41 pub async fn connect() -> Result<Self, ClientError> {
48 let inner = crate::connect().await?;
49 let client = Self { inner };
50 client.ensure_compatible().await?;
51 Ok(client)
52 }
53
54 pub async fn connect_to(path: &str) -> Result<Self, ClientError> {
60 let transport = tarpc::serde_transport::unix::connect(
61 path,
62 tarpc::tokio_serde::formats::Bincode::default,
63 )
64 .await?;
65 let inner =
66 crate::HelperServiceClient::new(tarpc::client::Config::default(), transport).spawn();
67 let client = Self { inner };
68 client.ensure_compatible().await?;
69 Ok(client)
70 }
71
72 pub async fn probe_version() -> Result<String, ClientError> {
78 let inner = crate::connect().await?;
79 Self { inner }.version().await
80 }
81
82 pub async fn route_add(&self, subnet: &str, iface: &str) -> Result<(), ClientError> {
84 Ok(self
85 .inner
86 .route_add(tarpc::context::current(), subnet.into(), iface.into())
87 .await??)
88 }
89
90 pub async fn route_remove(&self, subnet: &str) -> Result<(), ClientError> {
92 Ok(self
93 .inner
94 .route_remove(tarpc::context::current(), subnet.into())
95 .await??)
96 }
97
98 pub async fn dns_install(&self, domain: &str, port: u16) -> Result<(), ClientError> {
100 Ok(self
101 .inner
102 .dns_install(tarpc::context::current(), domain.into(), port)
103 .await??)
104 }
105
106 pub async fn dns_uninstall(&self, domain: &str) -> Result<(), ClientError> {
108 Ok(self
109 .inner
110 .dns_uninstall(tarpc::context::current(), domain.into())
111 .await??)
112 }
113
114 pub async fn dns_status(&self, domain: &str) -> Result<bool, ClientError> {
116 Ok(self
117 .inner
118 .dns_status(tarpc::context::current(), domain.into())
119 .await??)
120 }
121
122 pub async fn hosts_alias_install(&self) -> Result<(), ClientError> {
124 Ok(self
125 .inner
126 .hosts_alias_install(tarpc::context::current())
127 .await??)
128 }
129
130 pub async fn hosts_alias_uninstall(&self) -> Result<(), ClientError> {
132 Ok(self
133 .inner
134 .hosts_alias_uninstall(tarpc::context::current())
135 .await??)
136 }
137
138 pub async fn hosts_alias_status(&self) -> Result<bool, ClientError> {
140 Ok(self
141 .inner
142 .hosts_alias_status(tarpc::context::current())
143 .await??)
144 }
145
146 pub async fn socket_link(&self, target: &str) -> Result<(), ClientError> {
148 Ok(self
149 .inner
150 .socket_link(tarpc::context::current(), target.into())
151 .await??)
152 }
153
154 pub async fn socket_unlink(&self) -> Result<(), ClientError> {
156 Ok(self
157 .inner
158 .socket_unlink(tarpc::context::current())
159 .await??)
160 }
161
162 pub async fn cli_link(&self, name: &str, target: &str) -> Result<(), ClientError> {
164 Ok(self
165 .inner
166 .cli_link(tarpc::context::current(), name.into(), target.into())
167 .await??)
168 }
169
170 pub async fn cli_unlink(&self, name: &str) -> Result<(), ClientError> {
172 Ok(self
173 .inner
174 .cli_unlink(tarpc::context::current(), name.into())
175 .await??)
176 }
177
178 pub async fn version(&self) -> Result<String, ClientError> {
180 Ok(self.inner.version(tarpc::context::current()).await?)
181 }
182
183 async fn ensure_compatible(&self) -> Result<(), ClientError> {
184 let version = self.version().await?;
185 let installed = arcbox_constants::helper::parse_helper_version(&version)
186 .ok_or_else(|| ClientError::UnrecognizedVersion(version.clone()))?;
187 let required = arcbox_constants::helper::MIN_HELPER_VERSION;
188 let minimum = arcbox_constants::helper::parse_semver_triple(required)
189 .expect("MIN_HELPER_VERSION is covered by a unit test");
190
191 if arcbox_constants::helper::helper_version_satisfies(installed, minimum) {
192 Ok(())
193 } else {
194 Err(ClientError::IncompatibleVersion {
195 installed: version,
196 required,
197 })
198 }
199 }
200}