microsandbox_control_client/
json_client.rs1use std::path::Path;
4use std::sync::Arc;
5use std::time::Duration;
6
7use microsandbox_protocol::control::{ControlRequest, DEFAULT_REQUEST_TIMEOUT};
8use microsandbox_protocol_client::{
9 ClientError, ConnectOptions, Connector, Delivery, ErrorKind, LocalConnector, RequestOptions,
10};
11use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
12use tokio::time::{Instant, timeout_at};
13use tokio_util::sync::CancellationToken;
14use zeroize::Zeroizing;
15
16use crate::{
17 CheckedControlRequest, ControlClientError, ControlClientResult, ControlMode,
18 IntoControlMessage, JsonReply, dialer::Dialer,
19};
20
21pub const MAX_DISCOVERY_RESPONSE_SIZE: usize = 64 * 1024;
27
28#[derive(Clone)]
34pub struct JsonControlClient {
35 pub(crate) dialer: Dialer,
36 pub(crate) options: ConnectOptions,
37 closed: CancellationToken,
38 rediscover: bool,
39}
40
41impl JsonControlClient {
46 pub fn new(path: impl AsRef<Path>) -> Self {
48 Self::from_connector(Arc::new(LocalConnector::new(path)))
49 }
50
51 pub fn from_connector(connector: Arc<dyn Connector>) -> Self {
53 Self::configured(
54 Dialer::Unverified(connector),
55 ConnectOptions::default(),
56 false,
57 )
58 }
59
60 pub fn from_connector_with(
62 connector: Arc<dyn Connector>,
63 configure: impl FnOnce(ConnectOptions) -> ConnectOptions,
64 ) -> ControlClientResult<Self> {
65 let options = configure(ConnectOptions::default());
66 options.limits.validate()?;
67 Ok(Self::configured(
68 Dialer::Unverified(connector),
69 options,
70 false,
71 ))
72 }
73
74 pub(crate) fn configured(dialer: Dialer, options: ConnectOptions, rediscover: bool) -> Self {
75 Self {
76 dialer,
77 options,
78 closed: CancellationToken::new(),
79 rediscover,
80 }
81 }
82
83 pub fn is_closed(&self) -> bool {
85 self.closed.is_cancelled()
86 }
87
88 pub async fn closed(&self) {
91 self.closed.cancelled().await;
92 }
93
94 pub async fn close(&self) {
97 self.closed.cancel();
98 }
99
100 pub async fn request(
102 &self,
103 message: impl IntoControlMessage,
104 ) -> ControlClientResult<JsonReply> {
105 self.request_with(message, |options| options).await
106 }
107
108 pub async fn request_with(
110 &self,
111 message: impl IntoControlMessage,
112 configure: impl FnOnce(RequestOptions) -> RequestOptions,
113 ) -> ControlClientResult<JsonReply> {
114 let request = message.into_json()?;
115 self.operation(request, configure(RequestOptions::default()))
116 .await
117 }
118
119 pub async fn request_typed<R: CheckedControlRequest>(
121 &self,
122 request: &R,
123 ) -> ControlClientResult<R::Response> {
124 self.request_typed_with(request, |options| options).await
125 }
126
127 pub async fn request_typed_with<R: CheckedControlRequest>(
129 &self,
130 request: &R,
131 configure: impl FnOnce(RequestOptions) -> RequestOptions,
132 ) -> ControlClientResult<R::Response> {
133 let response = self
134 .operation(
135 request.json_request()?,
136 configure(RequestOptions::default()),
137 )
138 .await?;
139 request.decode_json(response)
140 }
141
142 pub(crate) async fn operation(
143 &self,
144 request: ControlRequest,
145 options: RequestOptions,
146 ) -> ControlClientResult<JsonReply> {
147 let until = deadline(
148 options
149 .request_timeout
150 .or(self.options.limits.request_timeout)
151 .unwrap_or(DEFAULT_REQUEST_TIMEOUT),
152 )?;
153 let setup_until = deadline(self.options.setup_timeout)?.min(until);
154 if self.rediscover && !self.dialer.verified() {
155 let mode = self
159 .discover(setup_until)
160 .await
161 .map_err(crate::connection::not_sent);
162 let mode = match mode {
163 Ok((mode, _)) => mode,
164 Err(error) => {
165 self.close().await;
166 return Err(error);
167 }
168 };
169 if mode != ControlMode::Json {
170 self.close().await;
171 return Err(ControlClientError::RuntimeChanged);
172 }
173 }
174 self.exchange(&request, until, setup_until, None).await
175 }
176
177 pub(crate) async fn discover(
178 &self,
179 until: Instant,
180 ) -> ControlClientResult<(ControlMode, crate::Capabilities)> {
181 let reply = self
182 .exchange(
183 &ControlRequest::Capabilities,
184 until,
185 until,
186 Some(MAX_DISCOVERY_RESPONSE_SIZE),
187 )
188 .await?;
189 let mode = reply.discovery_mode()?;
190 let capabilities = crate::json_reply::capabilities(
191 reply
192 .value()
193 .get("capabilities")
194 .ok_or_else(|| ClientError::new(ErrorKind::InvalidData))?,
195 )
196 .ok_or_else(|| ClientError::new(ErrorKind::InvalidData))?;
197 Ok((mode, capabilities))
198 }
199
200 async fn exchange(
201 &self,
202 request: &ControlRequest,
203 until: Instant,
204 setup_until: Instant,
205 max_reply: Option<usize>,
206 ) -> ControlClientResult<JsonReply> {
207 let mut line = Zeroizing::new(
208 serde_json::to_vec(request).map_err(|_| ClientError::new(ErrorKind::Encode))?,
209 );
210 line.push(b'\n');
211 let mut admitted = false;
212 let result = timeout_at(until, async {
213 tokio::select! {
214 biased;
215 _ = self.closed.cancelled() => Err(ClientError::new(ErrorKind::Closed).into()),
216 result = async {
217 check_deadline(setup_until)?;
218 let mut transport = timeout_at(setup_until, self.dialer.connect(setup_until)).await
219 .map_err(|_| ClientError::new(ErrorKind::Timeout))??;
220 check_deadline(setup_until)?;
221 timeout_at(setup_until, self.dialer.verify(setup_until)).await
222 .map_err(|_| ClientError::new(ErrorKind::Timeout))??;
223 check_deadline(until)?;
224 if self.closed.is_cancelled() { return Err(ClientError::new(ErrorKind::Closed).into()); }
225 admitted = true;
228 transport.write_all(&line).await.map_err(ClientError::from)?;
229 transport.flush().await.map_err(ClientError::from)?;
230 let mut reader = BufReader::new(transport);
231 let mut reply = Vec::new();
232 loop {
233 let buffer = reader.fill_buf().await.map_err(ClientError::from)?;
234 if buffer.is_empty() {
235 if reply.is_empty() { return Err(ClientError::new(ErrorKind::PeerClosed).into()); }
236 break;
237 }
238 let end = buffer.iter().position(|byte| *byte == b'\n').map(|index| index + 1);
239 let count = end.unwrap_or(buffer.len());
240 if max_reply.is_some_and(|limit| reply.len().saturating_add(count) > limit) {
241 return Err(ClientError::new(ErrorKind::InvalidData).into());
242 }
243 reply.extend_from_slice(&buffer[..count]);
244 reader.consume(count);
245 if end.is_some() { break; }
246 }
247 JsonReply::parse(reply)
248 } => result,
249 }
250 }).await.unwrap_or_else(|_| Err(ClientError::new(ErrorKind::Timeout).into()));
251 match result {
252 Ok(reply) => Ok(reply),
253 Err(error) => {
254 self.closed.cancel();
255 Err(match error {
256 ControlClientError::Client(error) => error
257 .with_delivery(if admitted {
258 Delivery::Unknown
259 } else {
260 Delivery::NotSent
261 })
262 .into(),
263 error => error,
264 })
265 }
266 }
267 }
268}
269
270pub(crate) fn deadline(duration: Duration) -> ControlClientResult<Instant> {
275 Instant::now()
276 .checked_add(duration)
277 .ok_or_else(|| ClientError::new(ErrorKind::InvalidOptions).into())
278}
279
280pub(crate) fn check_deadline(until: Instant) -> ControlClientResult<()> {
281 if Instant::now() >= until {
282 Err(ClientError::new(ErrorKind::Timeout).into())
283 } else {
284 Ok(())
285 }
286}